From aae8a69fd1a3c820f599991ae5b62043cc96bb12 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 6 Jun 2024 19:49:48 +0530 Subject: [PATCH 01/47] Add compiler support --- ballerina/http_annotation.bal | 19 +- ballerina/http_types.bal | 5 + .../stdlib/http/compiler/Constants.java | 8 + .../http/compiler/HttpCompilerPlugin.java | 8 +- .../http/compiler/HttpDiagnosticCodes.java | 24 +- .../HttpInterceptorResourceValidator.java | 3 +- .../compiler/HttpResourceFunctionNode.java | 89 ++++ .../http/compiler/HttpResourceValidator.java | 44 +- .../http/compiler/HttpServiceAnalyzer.java | 1 + .../HttpServiceContractResourceValidator.java | 205 ++++++++++ .../compiler/HttpServiceObjTypeAnalyzer.java | 108 +++++ .../http/compiler/HttpServiceValidator.java | 380 ++++++++++++++++-- .../codeaction/AddBasePathCodeAction.java | 100 +++++ .../codeaction/ChangeBasePathCodeAction.java | 101 +++++ .../http/compiler/codeaction/Constants.java | 1 + .../codeaction/ImplementServiceContract.java | 167 ++++++++ .../HttpPayloadParamIdentifier.java | 40 +- .../codemodifier/HttpServiceModifier.java | 3 +- .../PayloadAnnotationModifierTask.java | 29 +- .../codemodifier/ServiceTypeModifierTask.java | 171 ++++++++ 20 files changed, 1432 insertions(+), 74 deletions(-) create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddBasePathCodeAction.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeBasePathCodeAction.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index 9eb58f8b3c..e625bb0498 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -25,6 +25,7 @@ # + 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 public type HttpServiceConfig record {| string host = "b7a.default"; CompressionConfig compression = {}; @@ -35,6 +36,7 @@ public type HttpServiceConfig record {| boolean treatNilableAsOptional = true; byte[] openApiDefinition = []; boolean validation = true; + typedesc serviceType?; |}; # Configurations for CORS support. @@ -55,7 +57,7 @@ public type CorsConfig record {| |}; # The annotation which is used to configure an HTTP service. -public annotation HttpServiceConfig ServiceConfig on service; +public annotation HttpServiceConfig ServiceConfig on service, type; # Configuration for an HTTP resource. # @@ -87,7 +89,7 @@ public type HttpPayload record {| |}; # The annotation which is used to define the Payload resource signature parameter and return parameter. -public annotation HttpPayload Payload on parameter, return; +public const annotation HttpPayload Payload on parameter, return; # Configures the typing details type of the Caller resource signature parameter. # @@ -108,13 +110,13 @@ public type HttpHeader record {| |}; # The annotation which is used to define the Header resource signature parameter. -public annotation HttpHeader Header on parameter; +public const annotation HttpHeader Header on parameter; # Defines the query resource signature parameter. public type HttpQuery record {||}; # The annotation which is used to define the query resource signature parameter. -public annotation HttpQuery Query on parameter; +public const annotation HttpQuery Query on parameter; # Defines the HTTP response cache configuration. By default the `no-cache` directive is setted to the `cache-control` # header. In addition to that `etag` and `last-modified` headers are also added for cache validation. @@ -152,3 +154,12 @@ 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/ballerina/http_types.bal b/ballerina/http_types.bal index f4d95bab04..560111173f 100644 --- a/ballerina/http_types.bal +++ b/ballerina/http_types.bal @@ -28,6 +28,11 @@ public type Service distinct service object { }; +# The HTTP service contract type. +public type ServiceContract distinct service object { + *Service; +}; + # The types of data values that are expected by the HTTP `client` to return after the data binding operation. public type TargetType typedesc; 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 17f43a34ee..fc112e554d 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,6 +26,10 @@ 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"; public static final String SERVICE_KEYWORD = "service"; public static final String REMOTE_KEYWORD = "remote"; public static final String RESOURCE_KEYWORD = "resource"; @@ -72,8 +76,12 @@ private Constants() {} public static final String OBJECT = "object"; public static final String HEADER_OBJ_NAME = "Headers"; public static final String PAYLOAD_ANNOTATION = "Payload"; + public static final String HEADER_ANNOTATION = "Header"; + public static final String QUERY_ANNOTATION = "Query"; + 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 RESOURCE_CONFIG_ANNOTATION = "ResourceConfig"; 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 38e59beadb..4944e51e8a 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,15 +23,18 @@ 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; +import io.ballerina.stdlib.http.compiler.codeaction.ImplementServiceContract; import io.ballerina.stdlib.http.compiler.codemodifier.HttpServiceModifier; import io.ballerina.stdlib.http.compiler.completion.HttpServiceBodyContextProvider; @@ -60,7 +63,10 @@ private List getCodeActions() { new AddResponseContentTypeCodeAction(), new AddResponseCacheConfigCodeAction(), new AddInterceptorResourceMethodCodeAction(), - new AddInterceptorRemoteMethodCodeAction() + new AddInterceptorRemoteMethodCodeAction(), + new ImplementServiceContract(), + new AddBasePathCodeAction(), + new ChangeBasePathCodeAction() ); } 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 25f39a65c7..54cae8016b 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 @@ -22,7 +22,6 @@ import static io.ballerina.stdlib.http.compiler.Constants.ALLOWED_INTERCEPTOR_RETURN_UNION; import static io.ballerina.stdlib.http.compiler.Constants.ALLOWED_RETURN_UNION; -import static io.ballerina.stdlib.http.compiler.Constants.RESOURCE_CONFIG_ANNOTATION; import static io.ballerina.tools.diagnostics.DiagnosticSeverity.ERROR; import static io.ballerina.tools.diagnostics.DiagnosticSeverity.INTERNAL; @@ -33,8 +32,6 @@ public enum HttpDiagnosticCodes { HTTP_101("HTTP_101", "remote methods are not allowed in http:Service", ERROR), HTTP_102("HTTP_102", "invalid resource method return type: expected '" + ALLOWED_RETURN_UNION + "', but found '%s'", ERROR), - HTTP_103("HTTP_103", "invalid resource method annotation type: expected 'http:" + RESOURCE_CONFIG_ANNOTATION + - "', but found '%s'", ERROR), HTTP_104("HTTP_104", "invalid annotation type on param '%s': expected one of the following types: " + "'http:Payload', 'http:CallerInfo', 'http:Header', 'http:Query'", ERROR), HTTP_105("HTTP_105", "invalid resource parameter '%s'", ERROR), @@ -110,11 +107,30 @@ public enum HttpDiagnosticCodes { HTTP_151("HTTP_151", "ambiguous types for parameter '%s' and '%s'. Use annotations to avoid ambiguity", ERROR), HTTP_152("HTTP_152", "invalid union type for default payload param: '%s'. Use basic structured anydata types", 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_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 " + + "via the 'http:ServiceContract' type", ERROR), + HTTP_158("HTTP_158", "resource function which is not defined in the service contract type: '%s'," + + " is not allowed", ERROR), + HTTP_159("HTTP_159", "'http:ResourceConfig' 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_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), HTTP_HINT_103("HTTP_HINT_103", "Response content-type can be added", INTERNAL), - HTTP_HINT_104("HTTP_HINT_104", "Response cache configuration can be added", INTERNAL); + HTTP_HINT_104("HTTP_HINT_104", "Response cache configuration can be added", INTERNAL), + HTTP_HINT_105("HTTP_HINT_105", "Service contract: '%s', can be implemented", INTERNAL); private final String code; private final String message; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java index 7c54ae8bc2..f7a83a077f 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java @@ -44,7 +44,8 @@ public static void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefin if (isRequestErrorInterceptor(type)) { extractAndValidateMethodAndPath(ctx, member); } - HttpResourceValidator.extractInputParamTypeAndValidate(ctx, member, isRequestErrorInterceptor(type), + HttpResourceFunctionNode functionNode = new HttpResourceFunctionNode(member); + HttpResourceValidator.extractInputParamTypeAndValidate(ctx, functionNode, isRequestErrorInterceptor(type), typeSymbols); HttpCompilerPluginUtil.extractInterceptorReturnTypeAndValidate(ctx, typeSymbols, member, HttpDiagnosticCodes.HTTP_126); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java new file mode 100644 index 0000000000..d7c2dc99e9 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java @@ -0,0 +1,89 @@ +/* + * 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; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; +import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.tools.diagnostics.Location; + +import java.util.Objects; +import java.util.Optional; + +public class HttpResourceFunctionNode { + + Node functionNode; + MetadataNode metadata = null; + NodeList resourcePath; + FunctionSignatureNode functionSignatureNode; + IdentifierToken functionName; + + public HttpResourceFunctionNode(FunctionDefinitionNode functionDefinitionNode) { + functionNode = new FunctionDefinitionNode(functionDefinitionNode.internalNode(), + functionDefinitionNode.position(), functionDefinitionNode.parent()); + functionDefinitionNode.metadata().ifPresent(metadataNode -> metadata = metadataNode); + resourcePath = functionDefinitionNode.relativeResourcePath(); + functionSignatureNode = functionDefinitionNode.functionSignature(); + functionName = functionDefinitionNode.functionName(); + } + + public HttpResourceFunctionNode(MethodDeclarationNode methodDeclarationNode) { + functionNode = new MethodDeclarationNode(methodDeclarationNode.internalNode(), + methodDeclarationNode.position(), methodDeclarationNode.parent()); + methodDeclarationNode.metadata().ifPresent(metadataNode -> metadata = metadataNode); + resourcePath = methodDeclarationNode.relativeResourcePath(); + functionSignatureNode = methodDeclarationNode.methodSignature(); + functionName = methodDeclarationNode.methodName(); + } + + public Optional metadata() { + return Objects.isNull(metadata) ? Optional.empty() : Optional.of(metadata); + } + + public NodeList relativeResourcePath() { + return resourcePath; + } + + public FunctionSignatureNode functionSignature() { + return new FunctionSignatureNode(functionSignatureNode.internalNode(), functionSignatureNode.position(), + functionSignatureNode.parent()); + } + + public IdentifierToken functionName() { + return new IdentifierToken(functionName.internalNode(), functionName.position(), functionName.parent()); + } + + public Location location() { + return functionNode.location(); + } + + public Optional getFunctionDefinitionNode() { + return functionNode instanceof FunctionDefinitionNode ? + Optional.of((FunctionDefinitionNode) functionNode) : Optional.empty(); + } + + public Optional getSymbol(SemanticModel semanticModel) { + return semanticModel.symbol(functionNode); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java index 8250cfc39e..349d8553a5 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java @@ -43,6 +43,7 @@ import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; import io.ballerina.compiler.syntax.tree.MappingFieldNode; import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; import io.ballerina.compiler.syntax.tree.NameReferenceNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; @@ -134,14 +135,23 @@ private HttpResourceValidator() {} static void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, LinksMetaData linksMetaData, Map typeSymbols) { - extractResourceAnnotationAndValidate(ctx, member, linksMetaData); - extractInputParamTypeAndValidate(ctx, member, false, typeSymbols); - extractReturnTypeAndValidate(ctx, member, typeSymbols); + HttpResourceFunctionNode functionNode = new HttpResourceFunctionNode(member); + extractResourceAnnotationAndValidate(ctx, functionNode, linksMetaData); + extractInputParamTypeAndValidate(ctx, functionNode, false, typeSymbols); + extractReturnTypeAndValidate(ctx, functionNode, typeSymbols); validateHttpCallerUsage(ctx, member); } + static void validateResource(SyntaxNodeAnalysisContext ctx, MethodDeclarationNode member, + LinksMetaData linksMetaData, Map typeSymbols) { + HttpResourceFunctionNode functionNode = new HttpResourceFunctionNode(member); + extractResourceAnnotationAndValidate(ctx, functionNode, linksMetaData); + extractInputParamTypeAndValidate(ctx, functionNode, false, typeSymbols); + extractReturnTypeAndValidate(ctx, functionNode, typeSymbols); + } + private static void extractResourceAnnotationAndValidate(SyntaxNodeAnalysisContext ctx, - FunctionDefinitionNode member, + HttpResourceFunctionNode member, LinksMetaData linksMetaData) { Optional metadataNodeOptional = member.metadata(); if (metadataNodeOptional.isEmpty()) { @@ -155,14 +165,12 @@ private static void extractResourceAnnotationAndValidate(SyntaxNodeAnalysisConte String[] strings = annotName.split(Constants.COLON); if (RESOURCE_CONFIG_ANNOTATION.equals(strings[strings.length - 1].trim())) { validateLinksInResourceConfig(ctx, member, annotation, linksMetaData); - continue; } } - reportInvalidResourceAnnotation(ctx, annotReference.location(), annotName); } } - private static void validateLinksInResourceConfig(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, + private static void validateLinksInResourceConfig(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, AnnotationNode annotation, LinksMetaData linksMetaData) { Optional optionalMapping = annotation.annotValue(); if (optionalMapping.isEmpty()) { @@ -185,7 +193,7 @@ private static void validateLinksInResourceConfig(SyntaxNodeAnalysisContext ctx, } } - private static void validateResourceNameField(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, + private static void validateResourceNameField(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, SpecificFieldNode field, LinksMetaData linksMetaData) { Optional fieldValueExpression = field.valueExpr(); if (fieldValueExpression.isEmpty()) { @@ -206,7 +214,7 @@ private static void validateResourceNameField(SyntaxNodeAnalysisContext ctx, Fun } } - private static String getRelativePathFromFunctionNode(FunctionDefinitionNode member) { + private static String getRelativePathFromFunctionNode(HttpResourceFunctionNode member) { NodeList nodes = member.relativeResourcePath(); String path = EMPTY; for (Node node : nodes) { @@ -282,7 +290,7 @@ private static void populateLinkedToResources(SyntaxNodeAnalysisContext ctx, } } - public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, + public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, boolean isErrorInterceptor, Map typeSymbols) { boolean callerPresent = false; @@ -292,7 +300,7 @@ public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ct boolean errorPresent = false; boolean payloadAnnotationPresent = false; boolean headerAnnotationPresent = false; - Optional resourceMethodSymbolOptional = ctx.semanticModel().symbol(member); + Optional resourceMethodSymbolOptional = member.getSymbol(ctx.semanticModel()); Location paramLocation = member.location(); if (resourceMethodSymbolOptional.isEmpty()) { return; @@ -426,7 +434,10 @@ public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ct updateDiagnostic(ctx, paramLocation, HttpDiagnosticCodes.HTTP_115, paramName); } else { callerPresent = true; - extractCallerInfoValueAndValidate(ctx, member, paramIndex); + Optional functionDefNode = member.getFunctionDefinitionNode(); + if (functionDefNode.isPresent()) { + extractCallerInfoValueAndValidate(ctx, functionDefNode.get(), paramIndex); + } } } else { reportInvalidCallerParameterType(ctx, paramLocation, paramName); @@ -767,7 +778,7 @@ private static List getRespondParamNode(SyntaxNodeAnalys return respondNodeVisitor.getRespondStatementNodes(); } - private static void extractReturnTypeAndValidate(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, + private static void extractReturnTypeAndValidate(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, Map typeSymbols) { Optional returnTypeDescriptorNode = member.functionSignature().returnTypeDesc(); if (returnTypeDescriptorNode.isEmpty()) { @@ -775,7 +786,7 @@ private static void extractReturnTypeAndValidate(SyntaxNodeAnalysisContext ctx, } Node returnTypeNode = returnTypeDescriptorNode.get().type(); String returnTypeStringValue = HttpCompilerPluginUtil.getReturnTypeDescription(returnTypeDescriptorNode.get()); - Optional functionSymbol = ctx.semanticModel().symbol(member); + Optional functionSymbol = member.getSymbol(ctx.semanticModel()); if (functionSymbol.isEmpty()) { return; } @@ -923,11 +934,6 @@ private static boolean isValidReturnTypeWithCaller(TypeSymbol returnTypeDescript } } - private static void reportInvalidResourceAnnotation(SyntaxNodeAnalysisContext ctx, Location location, - String annotName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_103, annotName); - } - private static void reportInvalidParameterAnnotation(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_104, paramName); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java index e7d1041ac2..4bf17a52ab 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java @@ -28,6 +28,7 @@ public class HttpServiceAnalyzer extends CodeAnalyzer { @Override public void init(CodeAnalysisContext codeAnalysisContext) { + codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpServiceObjTypeAnalyzer(), SyntaxKind.OBJECT_TYPE_DESC); codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpServiceValidator(), SyntaxKind.SERVICE_DECLARATION); codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpInterceptorServiceValidator(), SyntaxKind.CLASS_DEFINITION); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java new file mode 100644 index 0000000000..e4d01e80ba --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java @@ -0,0 +1,205 @@ +/* + * 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; + +import io.ballerina.compiler.api.symbols.ResourceMethodSymbol; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.resourcepath.PathSegmentList; +import io.ballerina.compiler.api.symbols.resourcepath.ResourcePath; +import io.ballerina.compiler.api.symbols.resourcepath.util.PathSegment; +import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.DefaultableParameterNode; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.IncludedRecordParameterNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ParameterNode; +import io.ballerina.compiler.syntax.tree.RequiredParameterNode; +import io.ballerina.compiler.syntax.tree.RestParameterNode; +import io.ballerina.compiler.syntax.tree.ReturnTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Location; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static io.ballerina.stdlib.http.compiler.Constants.CACHE_ANNOTATION; +import static io.ballerina.stdlib.http.compiler.Constants.CALLER_ANNOTATION; +import static io.ballerina.stdlib.http.compiler.Constants.COLON; +import static io.ballerina.stdlib.http.compiler.Constants.HEADER_ANNOTATION; +import static io.ballerina.stdlib.http.compiler.Constants.HTTP; +import static io.ballerina.stdlib.http.compiler.Constants.PAYLOAD_ANNOTATION; +import static io.ballerina.stdlib.http.compiler.Constants.QUERY_ANNOTATION; +import static io.ballerina.stdlib.http.compiler.Constants.RESOURCE_CONFIG_ANNOTATION; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.updateDiagnostic; + +/** + * Validates a ballerina http resource implemented via the service contract type. + */ +public final class HttpServiceContractResourceValidator { + + private HttpServiceContractResourceValidator() { + } + + public static void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, + Set resourcesFromServiceType, String serviceTypeName) { + Optional functionDefinitionSymbol = ctx.semanticModel().symbol(member); + if (functionDefinitionSymbol.isEmpty() || + !(functionDefinitionSymbol.get() instanceof ResourceMethodSymbol resourceMethodSymbol)) { + return; + } + + ResourcePath resourcePath = resourceMethodSymbol.resourcePath(); + String resourceName = resourceMethodSymbol.getName().orElse("") + " " + constructResourcePathName(resourcePath); + if (!resourcesFromServiceType.contains(resourceName)) { + reportResourceFunctionNotAllowed(ctx, serviceTypeName, member.location()); + } + + validateAnnotationUsages(ctx, member); + } + + public static void validateAnnotationUsages(SyntaxNodeAnalysisContext ctx, + FunctionDefinitionNode resourceFunction) { + validateAnnotationUsagesOnResourceFunction(ctx, resourceFunction); + validateAnnotationUsagesOnInputParams(ctx, resourceFunction); + validateAnnotationUsagesOnReturnType(ctx, resourceFunction); + } + + public static void validateAnnotationUsagesOnResourceFunction(SyntaxNodeAnalysisContext ctx, + FunctionDefinitionNode resourceFunction) { + Optional metadataNodeOptional = resourceFunction.metadata(); + if (metadataNodeOptional.isEmpty()) { + return; + } + NodeList annotations = metadataNodeOptional.get().annotations(); + for (AnnotationNode annotation : annotations) { + Node annotReference = annotation.annotReference(); + String annotName = annotReference.toString(); + if (annotReference.kind() == SyntaxKind.QUALIFIED_NAME_REFERENCE) { + String[] annotStrings = annotName.split(Constants.COLON); + if (RESOURCE_CONFIG_ANNOTATION.equals(annotStrings[annotStrings.length - 1].trim()) + && HTTP.equals(annotStrings[0].trim())) { + reportResourceConfigAnnotationNotAllowed(ctx, annotation.location()); + } + } + } + } + + public static void validateAnnotationUsagesOnInputParams(SyntaxNodeAnalysisContext ctx, + FunctionDefinitionNode resourceFunction) { + SeparatedNodeList parameters = resourceFunction.functionSignature().parameters(); + for (ParameterNode parameter : parameters) { + NodeList annotations = getAnnotationsFromParameter(parameter); + for (AnnotationNode annotation : annotations) { + Node annotReference = annotation.annotReference(); + String annotName = annotReference.toString(); + if (annotReference.kind() == SyntaxKind.QUALIFIED_NAME_REFERENCE) { + String[] annotationStrings = annotName.split(COLON); + String annotationName = annotationStrings[annotationStrings.length - 1].trim(); + if (HTTP.equals(annotationStrings[0].trim()) && + (annotationName.equals(PAYLOAD_ANNOTATION) || annotationName.equals(HEADER_ANNOTATION) || + annotationName.equals(QUERY_ANNOTATION) || annotationName.equals(CALLER_ANNOTATION))) { + reportAnnotationNotAllowed(ctx, annotation.location(), HTTP + COLON + annotationName); + } + } + } + } + } + + public static NodeList getAnnotationsFromParameter(ParameterNode parameter) { + if (parameter instanceof RequiredParameterNode parameterNode) { + return parameterNode.annotations(); + } else if (parameter instanceof DefaultableParameterNode parameterNode) { + return parameterNode.annotations(); + } else if (parameter instanceof IncludedRecordParameterNode parameterNode) { + return parameterNode.annotations(); + } else if (parameter instanceof RestParameterNode parameterNode) { + return parameterNode.annotations(); + } else { + return AbstractNodeFactory.createEmptyNodeList(); + } + } + + public static void validateAnnotationUsagesOnReturnType(SyntaxNodeAnalysisContext ctx, + FunctionDefinitionNode resourceFunction) { + Optional returnTypeDescriptorNode = resourceFunction.functionSignature(). + returnTypeDesc(); + if (returnTypeDescriptorNode.isEmpty()) { + return; + } + + NodeList annotations = returnTypeDescriptorNode.get().annotations(); + for (AnnotationNode annotation : annotations) { + Node annotReference = annotation.annotReference(); + String annotName = annotReference.toString(); + if (annotReference.kind() == SyntaxKind.QUALIFIED_NAME_REFERENCE) { + String[] annotationStrings = annotName.split(COLON); + String annotationName = annotationStrings[annotationStrings.length - 1].trim(); + if (HTTP.equals(annotationStrings[0].trim()) && + (annotationName.equals(PAYLOAD_ANNOTATION) || annotationName.equals(CACHE_ANNOTATION))) { + reportAnnotationNotAllowed(ctx, annotation.location(), HTTP + COLON + annotationName); + } + } + } + } + + public static String constructResourcePathName(ResourcePath resourcePath) { + return switch (resourcePath.kind()) { + case DOT_RESOURCE_PATH -> "."; + case PATH_SEGMENT_LIST -> constructResourcePathNameFromSegList((PathSegmentList) resourcePath); + default -> "^^"; + }; + } + + public static String constructResourcePathNameFromSegList(PathSegmentList pathSegmentList) { + List resourcePaths = new ArrayList<>(); + for (PathSegment pathSegment : pathSegmentList.list()) { + switch (pathSegment.pathSegmentKind()) { + case NAMED_SEGMENT: + resourcePaths.add(pathSegment.getName().orElse("")); + break; + case PATH_PARAMETER: + resourcePaths.add("^"); + break; + default: + resourcePaths.add("^^"); + } + } + return resourcePaths.isEmpty() ? "" : String.join("/", resourcePaths); + } + + private static void reportResourceFunctionNotAllowed(SyntaxNodeAnalysisContext ctx, String serviceContractType, + Location location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_158, serviceContractType); + } + + private static void reportResourceConfigAnnotationNotAllowed(SyntaxNodeAnalysisContext ctx, Location location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_159); + } + + private static void reportAnnotationNotAllowed(SyntaxNodeAnalysisContext ctx, Location location, + String annotationName) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_160, annotationName); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java new file mode 100644 index 0000000000..e4cdea7fa5 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java @@ -0,0 +1,108 @@ +/* + * 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; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; + +import java.util.List; +import java.util.Optional; + +import static io.ballerina.stdlib.http.compiler.Constants.BALLERINA; +import static io.ballerina.stdlib.http.compiler.Constants.EMPTY; +import static io.ballerina.stdlib.http.compiler.Constants.HTTP; +import static io.ballerina.stdlib.http.compiler.Constants.HTTP_SERVICE_TYPE; +import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONTRACT_TYPE; + +public class HttpServiceObjTypeAnalyzer extends HttpServiceValidator { + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + List diagnostics = context.semanticModel().diagnostics(); + if (diagnostics.stream().anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity()))) { + return; + } + + Node typeNode = context.node(); + if (!isHttpServiceType(context.semanticModel(), typeNode)) { + return; + } + + ObjectTypeDescriptorNode serviceObjectType = (ObjectTypeDescriptorNode) typeNode; + Optional metadataNodeOptional = ((TypeDefinitionNode) serviceObjectType.parent()).metadata(); + metadataNodeOptional.ifPresent(metadataNode -> validateServiceAnnotation(context, metadataNode, null, + isServiceContractType(context.semanticModel(), serviceObjectType))); + + NodeList members = serviceObjectType.members(); + validateResources(context, members); + } + + public static boolean isServiceObjectType(ObjectTypeDescriptorNode typeNode) { + return typeNode.objectTypeQualifiers().stream().anyMatch( + qualifier -> qualifier.kind().equals(SyntaxKind.SERVICE_KEYWORD)); + } + + public static boolean isHttpServiceType(SemanticModel semanticModel, Node typeNode) { + if (!(typeNode instanceof ObjectTypeDescriptorNode serviceObjType) || !isServiceObjectType(serviceObjType)) { + return false; + } + + Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); + if (serviceObjSymbol.isEmpty() || + (!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) { + return false; + } + + Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, + HTTP_SERVICE_TYPE); + if (serviceContractType.isEmpty() || + !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { + return false; + } + + return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); + } + + private static boolean isServiceContractType(SemanticModel semanticModel, + ObjectTypeDescriptorNode serviceObjType) { + Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); + if (serviceObjSymbol.isEmpty() || + (!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) { + return false; + } + + Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, + SERVICE_CONTRACT_TYPE); + if (serviceContractType.isEmpty() || + !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { + return false; + } + + return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); + } +} 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 4e1caa9dad..eeea0a4bbe 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 @@ -18,44 +18,61 @@ 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; +import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; import io.ballerina.compiler.api.symbols.TypeDescKind; 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; import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; +import io.ballerina.compiler.syntax.tree.NodeLocation; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.Token; +import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; import io.ballerina.projects.plugins.AnalysisTask; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticFactory; import io.ballerina.tools.diagnostics.DiagnosticInfo; import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import io.ballerina.tools.diagnostics.Location; +import org.wso2.ballerinalang.compiler.diagnostic.BLangDiagnosticLocation; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import static io.ballerina.stdlib.http.compiler.Constants.BALLERINA; 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; import static io.ballerina.stdlib.http.compiler.Constants.HTTP; -import static io.ballerina.stdlib.http.compiler.Constants.INTERCEPTABLE_SERVICE; import static io.ballerina.stdlib.http.compiler.Constants.MEDIA_TYPE_SUBTYPE_PREFIX; import static io.ballerina.stdlib.http.compiler.Constants.MEDIA_TYPE_SUBTYPE_REGEX; 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; @@ -72,6 +89,7 @@ public class HttpServiceValidator implements AnalysisTask serviceTypeDesc = getServiceContractTypeDesc( + syntaxNodeAnalysisContext.semanticModel(), serviceDeclarationNode); + + if (serviceTypeDesc.isPresent() && !validateBasePathFromServiceType(syntaxNodeAnalysisContext, + serviceTypeDesc.get(), serviceDeclarationNode)) { + return; + } + + Optional metadataNodeOptional = serviceDeclarationNode.metadata(); + metadataNodeOptional.ifPresent(metadataNode -> validateServiceAnnotation(syntaxNodeAnalysisContext, + metadataNode, serviceTypeDesc.orElse(null), false)); - LinksMetaData linksMetaData = new LinksMetaData(); NodeList members = serviceDeclarationNode.members(); + if (serviceTypeDesc.isPresent()) { + Set resourcesFromServiceType = extractMethodsFromServiceType(serviceTypeDesc.get(), + syntaxNodeAnalysisContext.semanticModel()); + validateServiceContractResources(syntaxNodeAnalysisContext, resourcesFromServiceType, members, + serviceTypeDesc.get().toString().trim()); + } else { + validateResources(syntaxNodeAnalysisContext, members); + } + } + + public static boolean isServiceContractImplementation(SemanticModel semanticModel, ServiceDeclarationNode node) { + ServiceDeclarationNode serviceDeclarationNode = getServiceDeclarationNode(node, semanticModel); + if (serviceDeclarationNode == null) { + return false; + } + + return getServiceContractTypeDesc(semanticModel, serviceDeclarationNode).isPresent(); + } + + private static Optional getServiceContractTypeDesc(SemanticModel semanticModel, Node node) { + ServiceDeclarationNode serviceDeclarationNode = getServiceDeclarationNode(node, semanticModel); + if (serviceDeclarationNode == null) { + return Optional.empty(); + } + + return getServiceContractTypeDesc(semanticModel, serviceDeclarationNode); + } + + public static Optional getServiceContractTypeDesc(SemanticModel semanticModel, + ServiceDeclarationNode serviceDeclaration) { + Optional serviceTypeDesc = serviceDeclaration.typeDescriptor(); + if (serviceTypeDesc.isEmpty()) { + return Optional.empty(); + } + + Optional serviceTypeSymbol = semanticModel.symbol(serviceTypeDesc.get()); + if (serviceTypeSymbol.isEmpty() || + !(serviceTypeSymbol.get() instanceof TypeReferenceTypeSymbol serviceTypeRef)) { + return Optional.empty(); + } + + Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, + SERVICE_CONTRACT_TYPE); + if (serviceContractType.isEmpty() || + !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { + return Optional.empty(); + } + + if (serviceTypeRef.subtypeOf(serviceContractTypeDef.typeDescriptor())) { + return serviceTypeDesc; + } + return Optional.empty(); + } + + private static Set extractMethodsFromServiceType(TypeDescriptorNode serviceTypeDesc, + SemanticModel semanticModel) { + Optional serviceTypeSymbol = semanticModel.symbol(serviceTypeDesc); + if (serviceTypeSymbol.isEmpty() || + !(serviceTypeSymbol.get() instanceof TypeReferenceTypeSymbol serviceTypeRef)) { + return Collections.emptySet(); + } + + TypeSymbol serviceTypeRefSymbol = serviceTypeRef.typeDescriptor(); + if (!(serviceTypeRefSymbol instanceof ObjectTypeSymbol serviceObjTypeSymbol)) { + return Collections.emptySet(); + } + + 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(); + + 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("'", "")); + } + } + 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, + NodeList members) { + LinksMetaData linksMetaData = new LinksMetaData(); for (Node member : members) { if (member.kind() == SyntaxKind.OBJECT_METHOD_DEFINITION) { FunctionDefinitionNode node = (FunctionDefinitionNode) member; @@ -99,12 +293,60 @@ public void perform(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { } else if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { HttpResourceValidator.validateResource(syntaxNodeAnalysisContext, (FunctionDefinitionNode) member, linksMetaData, getCtxTypes(syntaxNodeAnalysisContext)); + } else if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DECLARATION) { + HttpResourceValidator.validateResource(syntaxNodeAnalysisContext, (MethodDeclarationNode) member, + linksMetaData, getCtxTypes(syntaxNodeAnalysisContext)); } } validateResourceLinks(syntaxNodeAnalysisContext, linksMetaData); } + private static void validateServiceContractResources(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, + Set resourcesFromServiceType, NodeList members, + String serviceTypeName) { + for (Node member : members) { + if (member.kind() == SyntaxKind.OBJECT_METHOD_DEFINITION) { + FunctionDefinitionNode node = (FunctionDefinitionNode) member; + NodeList tokens = node.qualifierList(); + if (tokens.isEmpty()) { + // Object methods are allowed. + continue; + } + if (tokens.stream().anyMatch(token -> token.text().equals(REMOTE_KEYWORD))) { + reportInvalidFunctionType(syntaxNodeAnalysisContext, node); + } + } else if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { + // Only resources defined in the serviceTypeDes is allowed + // No annotations are allowed in either in resource function or in the parameters + HttpServiceContractResourceValidator.validateResource(syntaxNodeAnalysisContext, + (FunctionDefinitionNode) member, resourcesFromServiceType, serviceTypeName); + } + } + } + + private static void checkForServiceImplementationErrors(SyntaxNodeAnalysisContext context) { + Node node = context.node(); + Optional serviceContractTypeDesc = getServiceContractTypeDesc(context.semanticModel(), + node); + if (serviceContractTypeDesc.isEmpty()) { + return; + } + String serviceType = serviceContractTypeDesc.get().toString().trim(); + + NodeLocation location = node.location(); + for (Diagnostic diagnostic : context.semanticModel().diagnostics()) { + Location diagnosticLocation = diagnostic.location(); + + if (diagnostic.message().contains("no implementation found for the method 'resource function") + && diagnosticLocation.textRange().equals(location.textRange()) + && diagnosticLocation.lineRange().equals(location.lineRange())) { + enableImplementServiceContractCodeAction(context, serviceType, location); + return; + } + } + } + public static boolean diagnosticContainsErrors(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { List diagnostics = syntaxNodeAnalysisContext.semanticModel().diagnostics(); return diagnostics.stream() @@ -112,8 +354,18 @@ public static boolean diagnosticContainsErrors(SyntaxNodeAnalysisContext syntaxN } public static ServiceDeclarationNode getServiceDeclarationNode(SyntaxNodeAnalysisContext context) { - ServiceDeclarationNode serviceDeclarationNode = (ServiceDeclarationNode) context.node(); - Optional serviceSymOptional = context.semanticModel().symbol(serviceDeclarationNode); + if (!(context.node() instanceof ServiceDeclarationNode serviceDeclarationNode)) { + return null; + } + return getServiceDeclarationNode(serviceDeclarationNode, context.semanticModel()); + } + + public static ServiceDeclarationNode getServiceDeclarationNode(Node node, SemanticModel semanticModel) { + if (!(node instanceof ServiceDeclarationNode serviceDeclarationNode)) { + return null; + } + + Optional serviceSymOptional = semanticModel.symbol(node); if (serviceSymOptional.isPresent()) { List listenerTypes = ((ServiceDeclarationSymbol) serviceSymOptional.get()).listenerTypes(); if (listenerTypes.stream().noneMatch(HttpServiceValidator::isListenerBelongsToHttpModule)) { @@ -146,7 +398,7 @@ public static TypeDescKind getReferencedTypeDescKind(TypeSymbol typeSymbol) { return kind; } - private void validateResourceLinks(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, + private static void validateResourceLinks(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, LinksMetaData linksMetaData) { if (!linksMetaData.hasNameReferenceObjects()) { for (Map linkedToResourceMap : linksMetaData.getLinkedToResourceMaps()) { @@ -159,7 +411,7 @@ private void validateResourceLinks(SyntaxNodeAnalysisContext syntaxNodeAnalysisC } } - private void checkLinkedResourceExistence(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, + private static void checkLinkedResourceExistence(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, LinksMetaData linksMetaData, LinkedToResource linkedToResource) { if (linksMetaData.getLinkedResourcesMap().containsKey(linkedToResource.getName())) { List linkedResources = @@ -195,14 +447,10 @@ private void checkLinkedResourceExistence(SyntaxNodeAnalysisContext syntaxNodeAn } } - private static void extractServiceAnnotationAndValidate(SyntaxNodeAnalysisContext ctx, - ServiceDeclarationNode serviceDeclarationNode) { - Optional metadataNodeOptional = serviceDeclarationNode.metadata(); - - if (metadataNodeOptional.isEmpty()) { - return; - } - NodeList annotations = metadataNodeOptional.get().annotations(); + protected static void validateServiceAnnotation(SyntaxNodeAnalysisContext ctx, MetadataNode metadataNode, + TypeDescriptorNode serviceTypeDesc, + boolean isServiceContractType) { + NodeList annotations = metadataNode.annotations(); for (AnnotationNode annotation : annotations) { Node annotReference = annotation.annotReference(); String annotName = annotReference.toString(); @@ -212,24 +460,53 @@ private static void extractServiceAnnotationAndValidate(SyntaxNodeAnalysisContex } String[] annotStrings = annotName.split(COLON); if (SERVICE_CONFIG_ANNOTATION.equals(annotStrings[annotStrings.length - 1].trim()) - && (annotValue.isPresent())) { - boolean isInterceptableService = false; - for (Node child:serviceDeclarationNode.children()) { - if (child.kind() == SyntaxKind.QUALIFIED_NAME_REFERENCE && - ((QualifiedNameReferenceNode) child).modulePrefix().text().equals(HTTP) && - ((QualifiedNameReferenceNode) child).identifier().text().equals(INTERCEPTABLE_SERVICE)) { - isInterceptableService = true; - break; - } + && HTTP.equals(annotStrings[0].trim())) { + if (Objects.nonNull(serviceTypeDesc)) { + validateAnnotationUsageForServiceContractType(ctx, annotation, annotValue.orElse(null), + 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()); + } + } + } + + private static void validateAnnotationUsageForServiceContractType(SyntaxNodeAnalysisContext ctx, + AnnotationNode annotation, + MappingConstructorExpressionNode annotValue, + TypeDescriptorNode typeDescriptorNode) { + if (Objects.isNull(annotValue) || annotValue.fields().isEmpty() || annotValue.fields().size() > 1) { + reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); + return; + } + + MappingFieldNode field = annotValue.fields().get(0); + String fieldString = field.toString(); + fieldString = fieldString.trim().replaceAll(UNNECESSARY_CHARS_REGEX, ""); + if (field.kind().equals(SyntaxKind.SPECIFIC_FIELD)) { + String[] strings = fieldString.split(COLON, 2); + if (SERVICE_TYPE.equals(strings[0].trim())) { + String expectedServiceType = typeDescriptorNode.toString().trim(); + String actualServiceType = strings[1].trim(); + if (!actualServiceType.equals(expectedServiceType)) { + reportInvalidServiceContractType(ctx, expectedServiceType, actualServiceType, + annotation.location()); } - validateServiceConfigAnnotation(ctx, annotValue, isInterceptableService); + return; } } + + reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); } - private static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext ctx, - Optional maps, - boolean isInterceptableService) { + protected static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext ctx, + Optional maps) { MappingConstructorExpressionNode mapping = maps.get(); for (MappingFieldNode field : mapping.fields()) { String fieldName = field.toString(); @@ -239,19 +516,20 @@ private static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext ct if (MEDIA_TYPE_SUBTYPE_PREFIX.equals(strings[0].trim())) { if (!(strings[1].trim().matches(MEDIA_TYPE_SUBTYPE_REGEX))) { reportInvalidMediaTypeSubtype(ctx, strings[1].trim(), field); - break; + continue; } if (strings[1].trim().contains(PLUS)) { String suffix = strings[1].trim().split(SUFFIX_SEPARATOR_REGEX, 2)[1]; reportErrorMediaTypeSuffix(ctx, suffix.trim(), field); - break; } + } else if (SERVICE_TYPE.equals(strings[0].trim())) { + reportServiceTypeNotAllowedFound(ctx, field.location()); } } } } - private void reportInvalidFunctionType(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode node) { + private static void reportInvalidFunctionType(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode node) { DiagnosticInfo diagnosticInfo = new DiagnosticInfo(HTTP_101.getCode(), HTTP_101.getMessage(), HTTP_101.getSeverity()); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, node.location())); @@ -284,4 +562,42 @@ private static void reportUnresolvedLinkedResourceWithMethod(SyntaxNodeAnalysisC updateDiagnostic(ctx, resource.getNode().location(), HttpDiagnosticCodes.HTTP_150, resource.getMethod(), resource.getName()); } + + private static void reportInvalidServiceConfigAnnotationUsage(SyntaxNodeAnalysisContext ctx, Location location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_153); + } + + private static void reportInvalidServiceContractType(SyntaxNodeAnalysisContext ctx, String expectedServiceType, + String actualServiceType, Location location) { + 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) { + 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); + } + + private static void reportServiceTypeNotAllowedFound(SyntaxNodeAnalysisContext ctx, NodeLocation location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_157); + } + + private static void reportServiceContractTypeAnnotationNotAllowedFound(SyntaxNodeAnalysisContext ctx, + NodeLocation location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_161); + } + + private static void enableImplementServiceContractCodeAction(SyntaxNodeAnalysisContext ctx, String serviceType, + NodeLocation location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_HINT_105, 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 new file mode 100644 index 0000000000..0138b085bb --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddBasePathCodeAction.java @@ -0,0 +1,100 @@ +/* + * 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; + +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 new file mode 100644 index 0000000000..d81fc995d4 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeBasePathCodeAction.java @@ -0,0 +1,101 @@ +/* + * 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; + +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/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/Constants.java index d1cd8715bf..9bbcdc9eea 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/Constants.java @@ -26,6 +26,7 @@ private Constants () {} public static final String NODE_LOCATION_KEY = "node.location"; public static final String IS_ERROR_INTERCEPTOR_TYPE = "node.errorInterceptor"; + public static final String EXPECTED_BASE_PATH = "expectedBasePath"; public static final String REMOTE = "remote"; public static final String RESOURCE = "resource"; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java new file mode 100644 index 0000000000..a8a0abb3a8 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java @@ -0,0 +1,167 @@ +/* + * 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.api.SemanticModel; +import io.ballerina.compiler.api.symbols.MethodSymbol; +import io.ballerina.compiler.api.symbols.ObjectTypeSymbol; +import io.ballerina.compiler.api.symbols.ResourceMethodSymbol; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; +import io.ballerina.compiler.api.symbols.TypeSymbol; +import io.ballerina.compiler.api.symbols.resourcepath.ResourcePath; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.NonTerminalNode; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; +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.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static io.ballerina.stdlib.http.compiler.HttpServiceContractResourceValidator.constructResourcePathName; +import static io.ballerina.stdlib.http.compiler.HttpServiceValidator.getServiceContractTypeDesc; +import static io.ballerina.stdlib.http.compiler.codeaction.Constants.LS; +import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; + +public class ImplementServiceContract implements CodeAction { + @Override + public List supportedDiagnosticCodes() { + return List.of(HttpDiagnosticCodes.HTTP_HINT_105.getCode()); + } + + @Override + public Optional codeActionInfo(CodeActionContext context) { + NonTerminalNode node = CodeActionUtil.findNode(context.currentDocument().syntaxTree(), + context.diagnostic().location().lineRange()); + if (!node.kind().equals(SyntaxKind.SERVICE_DECLARATION)) { + return Optional.empty(); + } + + CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); + return Optional.of(CodeActionInfo.from("Implement all the resource methods from service contract", + List.of(locationArg))); + } + + @Override + public List execute(CodeActionExecutionContext context) { + LineRange lineRange = null; + for (CodeActionArgument argument : context.arguments()) { + if (NODE_LOCATION_KEY.equals(argument.key())) { + lineRange = argument.valueAs(LineRange.class); + } + } + + if (lineRange == null) { + return Collections.emptyList(); + } + + SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); + SemanticModel semanticModel = context.currentSemanticModel(); + NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange); + if (!node.kind().equals(SyntaxKind.SERVICE_DECLARATION)) { + return Collections.emptyList(); + } + + Optional serviceTypeDesc = getServiceContractTypeDesc(semanticModel, + (ServiceDeclarationNode) node); + if (serviceTypeDesc.isEmpty()) { + return Collections.emptyList(); + } + + Optional serviceTypeSymbol = semanticModel.symbol(serviceTypeDesc.get()); + if (serviceTypeSymbol.isEmpty() || + !(serviceTypeSymbol.get() instanceof TypeReferenceTypeSymbol serviceTypeRef)) { + return Collections.emptyList(); + } + + TypeSymbol serviceTypeRefSymbol = serviceTypeRef.typeDescriptor(); + if (!(serviceTypeRefSymbol instanceof ObjectTypeSymbol serviceObjTypeSymbol)) { + return Collections.emptyList(); + } + + NodeList members = ((ServiceDeclarationNode) node).members(); + List existingMethods = new ArrayList<>(); + for (Node member : members) { + if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { + Optional functionDefinitionSymbol = semanticModel.symbol(member); + if (functionDefinitionSymbol.isEmpty() || + !(functionDefinitionSymbol.get() instanceof ResourceMethodSymbol resourceMethodSymbol)) { + continue; + } + ResourcePath resourcePath = resourceMethodSymbol.resourcePath(); + existingMethods.add(resourceMethodSymbol.getName().orElse("") + " " + + constructResourcePathName(resourcePath)); + } + } + + Map methodSymbolMap = serviceObjTypeSymbol.methods(); + StringBuilder methods = new StringBuilder(); + for (Map.Entry entry : methodSymbolMap.entrySet()) { + if (existingMethods.contains(entry.getKey())) { + continue; + } + MethodSymbol methodSymbol = entry.getValue(); + if (methodSymbol instanceof ResourceMethodSymbol resourceMethodSymbol) { + methods.append(getMethodSignature(resourceMethodSymbol)); + } + } + + TextRange textRange = TextRange.from(((ServiceDeclarationNode) node).closeBraceToken(). + textRange().startOffset(), 0); + List textEdits = new ArrayList<>(); + textEdits.add(TextEdit.from(textRange, methods.toString())); + 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))); + } + + private String getMethodSignature(ResourceMethodSymbol resourceMethodSymbol) { + return LS + "\t" + sanitizePackageNames(resourceMethodSymbol.signature()) + " {" + LS + LS + "\t}" + LS; + } + + private String sanitizePackageNames(String input) { + Pattern pattern = Pattern.compile("(\\w+)/(\\w+:)(\\d+.\\d+.\\d+):"); + Matcher matcher = pattern.matcher(input); + return matcher.replaceAll("$2"); + } + + @Override + public String name() { + return "IMPLEMENT_SERVICE_CONTRACT"; + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java index 7bf304e6a6..c7ba2a8f60 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java @@ -27,8 +27,10 @@ import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.Token; @@ -37,6 +39,7 @@ import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.stdlib.http.compiler.Constants; import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; +import io.ballerina.stdlib.http.compiler.HttpResourceFunctionNode; import io.ballerina.stdlib.http.compiler.HttpResourceValidator; import io.ballerina.stdlib.http.compiler.HttpServiceValidator; import io.ballerina.stdlib.http.compiler.codemodifier.context.DocumentContext; @@ -69,6 +72,7 @@ import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.getEffectiveType; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.isValidBasicParamType; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.isValidNilableBasicParamType; +import static io.ballerina.stdlib.http.compiler.HttpServiceObjTypeAnalyzer.isHttpServiceType; /** @@ -94,6 +98,9 @@ public void perform(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { validateServiceDeclaration(syntaxNodeAnalysisContext, typeSymbols); } else if (kind == SyntaxKind.CLASS_DEFINITION) { validateClassDefinition(syntaxNodeAnalysisContext, typeSymbols); + } else if (kind == SyntaxKind.OBJECT_TYPE_DESC && isHttpServiceType(syntaxNodeAnalysisContext.semanticModel(), + syntaxNodeAnalysisContext.node())) { + validateServiceObjDefinition(syntaxNodeAnalysisContext, typeSymbols); } } @@ -105,9 +112,27 @@ private void validateServiceDeclaration(SyntaxNodeAnalysisContext syntaxNodeAnal } NodeList members = serviceDeclarationNode.members(); ServiceContext serviceContext = new ServiceContext(serviceDeclarationNode.hashCode()); + validateResources(syntaxNodeAnalysisContext, typeSymbols, members, serviceContext); + } + + private void validateServiceObjDefinition(SyntaxNodeAnalysisContext context, Map typeSymbols) { + ObjectTypeDescriptorNode serviceObjType = (ObjectTypeDescriptorNode) context.node(); + NodeList members = serviceObjType.members(); + ServiceContext serviceContext = new ServiceContext(serviceObjType.hashCode()); + validateResources(context, typeSymbols, members, serviceContext); + } + + private void validateResources(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, + Map typeSymbols, NodeList members, + ServiceContext serviceContext) { for (Node member : members) { if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { - validateResource(syntaxNodeAnalysisContext, (FunctionDefinitionNode) member, serviceContext, + validateResource(syntaxNodeAnalysisContext, + new HttpResourceFunctionNode((FunctionDefinitionNode) member), serviceContext, + typeSymbols); + } else if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DECLARATION) { + validateResource(syntaxNodeAnalysisContext, + new HttpResourceFunctionNode((MethodDeclarationNode) member), serviceContext, typeSymbols); } } @@ -141,24 +166,19 @@ private void validateClassDefinition(SyntaxNodeAnalysisContext syntaxNodeAnalysi } } if (proceed) { - for (Node member : members) { - if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { - validateResource(syntaxNodeAnalysisContext, (FunctionDefinitionNode) member, serviceContext, - typeSymbols); - } - } + validateResources(syntaxNodeAnalysisContext, typeSymbols, members, serviceContext); } } - void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, ServiceContext serviceContext, + void validateResource(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, ServiceContext serviceContext, Map typeSymbols) { extractInputParamTypeAndValidate(ctx, member, serviceContext, typeSymbols); } - void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, + void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, ServiceContext serviceContext, Map typeSymbols) { - Optional resourceMethodSymbolOptional = ctx.semanticModel().symbol(member); + Optional resourceMethodSymbolOptional = member.getSymbol(ctx.semanticModel()); int resourceId = member.hashCode(); if (resourceMethodSymbolOptional.isEmpty()) { return; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java index 4aa6227431..c2ad4ea204 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java @@ -44,7 +44,8 @@ public HttpServiceModifier() { public void init(CodeModifierContext codeModifierContext) { codeModifierContext.addSyntaxNodeAnalysisTask( new HttpPayloadParamIdentifier(this.payloadParamContextMap), - List.of(SyntaxKind.SERVICE_DECLARATION, SyntaxKind.CLASS_DEFINITION)); + List.of(SyntaxKind.SERVICE_DECLARATION, SyntaxKind.CLASS_DEFINITION, SyntaxKind.OBJECT_TYPE_DESC)); codeModifierContext.addSourceModifierTask(new PayloadAnnotationModifierTask(this.payloadParamContextMap)); + codeModifierContext.addSourceModifierTask(new ServiceTypeModifierTask()); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java index 86f7d9242b..b66f6fc2e0 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java @@ -29,6 +29,7 @@ import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeFactory; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ParameterNode; import io.ballerina.compiler.syntax.tree.RequiredParameterNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; @@ -37,6 +38,7 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.SyntaxTree; import io.ballerina.compiler.syntax.tree.Token; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; import io.ballerina.projects.Module; @@ -54,6 +56,9 @@ import java.util.List; import java.util.Map; +import static io.ballerina.stdlib.http.compiler.HttpServiceObjTypeAnalyzer.isHttpServiceType; +import static io.ballerina.stdlib.http.compiler.HttpServiceValidator.isServiceContractImplementation; + /** * {@code HttpPayloadParamIdentifier} injects the @http:Payload annotation to the Payload param which found during the * initial analysis. @@ -108,7 +113,9 @@ private NodeList updateMemberNodes(NodeList members; - if (memberNode.kind() == SyntaxKind.SERVICE_DECLARATION) { + if (memberNode.kind() == SyntaxKind.SERVICE_DECLARATION && + !isServiceContractImplementation(documentContext.getContext().semanticModel(), + (ServiceDeclarationNode) memberNode)) { ServiceDeclarationNode serviceNode = (ServiceDeclarationNode) memberNode; serviceId = serviceNode.hashCode(); members = serviceNode.members(); @@ -116,6 +123,12 @@ private NodeList updateMemberNodes(NodeList updateMemberNodes(NodeList { + + @Override + public void modify(SourceModifierContext modifierContext) { + boolean erroneousCompilation = modifierContext.compilation().diagnosticResult() + .diagnostics().stream() + .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); + if (erroneousCompilation) { + return; + } + + modifyServiceDeclarationNodes(modifierContext); + } + + private void modifyServiceDeclarationNodes(SourceModifierContext modifierContext) { + Package currentPackage = modifierContext.currentPackage(); + for (ModuleId moduleId : currentPackage.moduleIds()) { + modifyServiceDeclarationsPerModule(modifierContext, moduleId, currentPackage); + } + } + + private void modifyServiceDeclarationsPerModule(SourceModifierContext modifierContext, ModuleId moduleId, + Package currentPackage) { + Module currentModule = currentPackage.module(moduleId); + for (DocumentId documentId : currentModule.documentIds()) { + modifyServiceDeclarationsPerDocument(modifierContext, documentId, currentModule); + } + } + + private void modifyServiceDeclarationsPerDocument(SourceModifierContext modifierContext, DocumentId documentId, + Module currentModule) { + Document currentDoc = currentModule.document(documentId); + ModulePartNode rootNode = currentDoc.syntaxTree().rootNode(); + NodeList newMembers = updateMemberNodes(rootNode.members()); + ModulePartNode newModulePart = rootNode.modify(rootNode.imports(), newMembers, rootNode.eofToken()); + SyntaxTree updatedSyntaxTree = currentDoc.syntaxTree().modifyWith(newModulePart); + TextDocument textDocument = updatedSyntaxTree.textDocument(); + if (currentModule.documentIds().contains(documentId)) { + modifierContext.modifySourceFile(textDocument, documentId); + } else { + modifierContext.modifyTestSourceFile(textDocument, documentId); + } + } + + private NodeList updateMemberNodes(NodeList oldMembers) { + List updatedMembers = new ArrayList<>(); + for (ModuleMemberDeclarationNode memberNode : oldMembers) { + if (memberNode.kind().equals(SyntaxKind.SERVICE_DECLARATION)) { + updatedMembers.add(updateServiceDeclarationNode((ServiceDeclarationNode) memberNode)); + } else { + updatedMembers.add(memberNode); + } + } + return AbstractNodeFactory.createNodeList(updatedMembers); + } + + private ServiceDeclarationNode updateServiceDeclarationNode(ServiceDeclarationNode serviceDeclarationNode) { + Optional serviceTypeDesc = serviceDeclarationNode.typeDescriptor(); + if (serviceTypeDesc.isEmpty()) { + return serviceDeclarationNode; + } + + Optional metadataNodeOptional = serviceDeclarationNode.metadata(); + if (metadataNodeOptional.isEmpty()) { + return addServiceConfigAnnotation(serviceTypeDesc.get(), serviceDeclarationNode); + } + + NodeList annotations = metadataNodeOptional.get().annotations(); + for (AnnotationNode annotation : annotations) { + Node annotReference = annotation.annotReference(); + String annotName = annotReference.toString(); + if (annotReference.kind() != SyntaxKind.QUALIFIED_NAME_REFERENCE) { + continue; + } + String[] annotStrings = annotName.split(COLON); + if (SERVICE_CONFIG_ANNOTATION.equals(annotStrings[annotStrings.length - 1].trim()) + && HTTP.equals(annotStrings[0].trim())) { + return serviceDeclarationNode; + } + } + + return addServiceConfigAnnotation(serviceTypeDesc.get(), serviceDeclarationNode); + } + + private ServiceDeclarationNode addServiceConfigAnnotation(TypeDescriptorNode serviceTypeDesc, + ServiceDeclarationNode serviceDeclarationNode) { + SpecificFieldNode serviceTypeField = createSpecificFieldNode(null, createIdentifierToken("serviceType"), + createToken(COLON_TOKEN), serviceTypeDesc); + MappingConstructorExpressionNode serviceConfigConstruct = createMappingConstructorExpressionNode( + createToken(SyntaxKind.OPEN_BRACE_TOKEN), createSeparatedNodeList(serviceTypeField), + createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); + AnnotationNode serviceConfigAnnotation = createAnnotationNode(createToken(AT_TOKEN), + createQualifiedNameReferenceNode(createIdentifierToken(HTTP), createToken(COLON_TOKEN), + createIdentifierToken(SERVICE_CONFIG_ANNOTATION)), serviceConfigConstruct); + Optional metadata = serviceDeclarationNode.metadata(); + MetadataNode metadataNode; + if (metadata.isEmpty()) { + metadataNode = createMetadataNode(null, createNodeList(serviceConfigAnnotation)); + } else { + NodeList annotations = metadata.get().annotations().add(serviceConfigAnnotation); + metadataNode = metadata.get().modify().withAnnotations(annotations).apply(); + } + return serviceDeclarationNode.modify().withMetadata(metadataNode).apply(); + } +} From 98b410c174a21f259cef5ae90ecb857c1a5f8667 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 12 Jun 2024 08:15:37 +0530 Subject: [PATCH 02/47] Add runtime support --- .../stdlib/http/api/HTTPServicesRegistry.java | 34 +++++++++- .../stdlib/http/api/HttpService.java | 28 ++++---- .../http/api/HttpServiceFromContract.java | 65 +++++++++++++++++++ 3 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java 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 9bca326543..cc6c2606b5 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 @@ -21,19 +21,27 @@ import io.ballerina.runtime.api.Runtime; import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.types.ReferenceType; +import io.ballerina.runtime.api.types.Type; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; +import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import static io.ballerina.stdlib.http.api.HttpConstants.DEFAULT_HOST; +import static io.ballerina.stdlib.http.api.HttpUtil.checkConfigAnnotationAvailability; /** * This services registry holds all the services of HTTP + WebSocket. This is a singleton class where all HTTP + @@ -44,6 +52,7 @@ public class HTTPServicesRegistry { private static final Logger logger = LoggerFactory.getLogger(HTTPServicesRegistry.class); + private static final BString SERVICE_TYPE = StringUtils.fromString("serviceType"); protected Map servicesMapByHost = new ConcurrentHashMap<>(); protected Map servicesByBasePath; @@ -98,7 +107,10 @@ public List getSortedServiceURIsByHost(String hostName) { * @param basePath absolute resource path of the service */ public void registerService(BObject service, String basePath) { - HttpService httpService = HttpService.buildHttpService(service, basePath); + Optional serviceContractType = getServiceContractType(service); + HttpService httpService = serviceContractType.map(referenceType -> + HttpServiceFromContract.buildHttpService(service, basePath, referenceType)).orElseGet( + () -> HttpService.buildHttpService(service, basePath)); service.addNativeData(HttpConstants.ABSOLUTE_RESOURCE_PATH, basePath); String hostName = httpService.getHostName(); if (servicesMapByHost.get(hostName) == null) { @@ -127,6 +139,26 @@ public void registerService(BObject service, String basePath) { sortedServiceURIs.sort((basePath1, basePath2) -> basePath2.length() - basePath1.length()); } + + private static Optional getServiceContractType(BObject service) { + BMap serviceConfig = HttpService.getHttpServiceConfigAnnotation(service); + if (!checkConfigAnnotationAvailability(serviceConfig)) { + return Optional.empty(); + } + + Object serviceType = ((BMap) serviceConfig).get(SERVICE_TYPE); + if (Objects.isNull(serviceType) || !(serviceType instanceof BTypedesc serviceTypeDesc)) { + return Optional.empty(); + } + + Type serviceContractType = serviceTypeDesc.getDescribingType(); + if (Objects.isNull(serviceContractType) || + !(serviceContractType instanceof ReferenceType serviceContractRefType)) { + return Optional.empty(); + } + return Optional.of(serviceContractRefType); + } + public String findTheMostSpecificBasePath(String requestURIPath, Map services, List sortedServiceURIs) { for (Object key : sortedServiceURIs) { diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index 86b0318411..bb8553990b 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -26,6 +26,7 @@ import io.ballerina.runtime.api.types.ArrayType; import io.ballerina.runtime.api.types.MethodType; import io.ballerina.runtime.api.types.ObjectType; +import io.ballerina.runtime.api.types.ResourceMethodType; import io.ballerina.runtime.api.types.ServiceType; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.TypeUtils; @@ -71,13 +72,13 @@ public class HttpService implements Service { private static final Logger log = LoggerFactory.getLogger(HttpService.class); protected static final BString BASE_PATH_FIELD = fromString("basePath"); - private static final BString CORS_FIELD = fromString("cors"); + protected static final BString CORS_FIELD = fromString("cors"); private static final BString VERSIONING_FIELD = fromString("versioning"); - private static final BString HOST_FIELD = fromString("host"); - private static final BString OPENAPI_DEF_FIELD = fromString("openApiDefinition"); - private static final BString MEDIA_TYPE_SUBTYPE_PREFIX = fromString("mediaTypeSubtypePrefix"); - private static final BString TREAT_NILABLE_AS_OPTIONAL = fromString("treatNilableAsOptional"); - private static final BString DATA_VALIDATION = fromString("validation"); + protected static final BString HOST_FIELD = fromString("host"); + protected static final BString OPENAPI_DEF_FIELD = fromString("openApiDefinition"); + protected static final BString MEDIA_TYPE_SUBTYPE_PREFIX = fromString("mediaTypeSubtypePrefix"); + protected static final BString TREAT_NILABLE_AS_OPTIONAL = fromString("treatNilableAsOptional"); + protected static final BString DATA_VALIDATION = fromString("validation"); private BObject balService; private List resources; @@ -115,7 +116,7 @@ public void setKeepAlive(boolean keepAlive) { this.keepAlive = keepAlive; } - private void setCompressionConfig(BMap compression) { + protected void setCompressionConfig(BMap compression) { this.compression = compression; } @@ -259,10 +260,9 @@ public static HttpService buildHttpService(BObject service, String basePath) { return httpService; } - private static void processResources(HttpService httpService) { + protected static void processResources(HttpService httpService) { List httpResources = new ArrayList<>(); - for (MethodType resource : ((ServiceType) TypeUtils.getType( - httpService.getBalService())).getResourceMethods()) { + for (MethodType resource : httpService.getResourceMethods()) { if (!SymbolFlags.isFlagOn(resource.getFlags(), SymbolFlags.RESOURCE)) { continue; } @@ -280,6 +280,10 @@ private static void processResources(HttpService httpService) { httpService.setResources(httpResources); } + protected ResourceMethodType[] getResourceMethods() { + return ((ServiceType) TypeUtils.getType(balService)).getResourceMethods(); + } + private static void processLinks(HttpService httpService, List httpResources) { for (HttpResource targetResource : httpResources) { for (HttpResource.LinkedResourceInfo link : targetResource.getLinkedResources()) { @@ -393,7 +397,7 @@ private static void updateResourceTree(HttpService httpService, List) serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_COMPRESSION)); + httpService.setChunkingConfig(serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_CHUNKING).toString()); + httpService.setCorsHeaders(CorsHeaders.buildCorsHeaders(serviceConfig.getMapValue(CORS_FIELD))); + httpService.setHostName(serviceConfig.getStringValue(HOST_FIELD).getValue().trim()); + httpService.setIntrospectionPayload(serviceConfig.getArrayValue(OPENAPI_DEF_FIELD).getByteArray()); + if (serviceConfig.containsKey(MEDIA_TYPE_SUBTYPE_PREFIX)) { + httpService.setMediaTypeSubtypePrefix(serviceConfig.getStringValue(MEDIA_TYPE_SUBTYPE_PREFIX) + .getValue().trim()); + } + httpService.setTreatNilableAsOptional(serviceConfig.getBooleanValue(TREAT_NILABLE_AS_OPTIONAL)); + httpService.setConstraintValidation(serviceConfig.getBooleanValue(DATA_VALIDATION)); + } else { + httpService.setHostName(HttpConstants.DEFAULT_HOST); + } + processResources(httpService); + httpService.setAllAllowedMethods(DispatcherUtil.getAllResourceMethods(httpService)); + return httpService; + } + + public static BMap getHttpServiceConfigAnnotation(ReferenceType serviceContractType) { + String packagePath = ModuleUtils.getHttpPackageIdentifier(); + String annotationName = HttpConstants.ANN_NAME_HTTP_SERVICE_CONFIG; + String key = packagePath.replaceAll(HttpConstants.REGEX, HttpConstants.SINGLE_SLASH); + if (!(serviceContractType instanceof AnnotatableType annotatableServiceContractType)) { + return null; + } + return (BMap) annotatableServiceContractType.getAnnotation(fromString(key + ":" + annotationName)); + } + + @Override + protected ResourceMethodType[] getResourceMethods() { + return ((ServiceType) TypeUtils.getReferredType(serviceContractType)).getResourceMethods(); + } +} From aceee23e0aef7ce43ecdaf3a74ba1755fb12344d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 12 Jun 2024 10:54:55 +0530 Subject: [PATCH 03/47] Fix test cases --- .../http/compiler/CompilerPluginTest.java | 37 +++++++------------ .../compiler/CompilerPluginTestConstants.java | 1 - 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java index 3d6fc2edda..e2f57d478f 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java @@ -133,16 +133,6 @@ public void testInValidReturnTypes() { "'anydata|http:Response|http:StatusCodeResponse|error', but found 'readonly & error[]'", HTTP_102); } - @Test - public void testInValidAnnotations() { - Package currentPackage = loadPackage("sample_package_3"); - PackageCompilation compilation = currentPackage.getCompilation(); - DiagnosticResult diagnosticResult = compilation.diagnosticResult(); - Assert.assertEquals(diagnosticResult.errorCount(), 1); - assertError(diagnosticResult, 0, "invalid resource method annotation type: expected 'http:ResourceConfig', " + - "but found 'display '", CompilerPluginTestConstants.HTTP_103); - } - @Test public void testInValidInputPayloadArgs() { Package currentPackage = loadPackage("sample_package_4"); @@ -410,22 +400,21 @@ public void testResourceErrorPositions() { Package currentPackage = loadPackage("sample_package_15"); PackageCompilation compilation = currentPackage.getCompilation(); DiagnosticResult diagnosticResult = compilation.diagnosticResult(); - Assert.assertEquals(diagnosticResult.errorCount(), 14); + Assert.assertEquals(diagnosticResult.errorCount(), 13); // only testing the error locations assertErrorPosition(diagnosticResult, 0, "(29:44,29:60)"); - assertErrorPosition(diagnosticResult, 1, "(34:5,34:12)"); - assertErrorPosition(diagnosticResult, 2, "(42:86,42:87)"); - assertErrorPosition(diagnosticResult, 3, "(46:57,46:60)"); - assertErrorPosition(diagnosticResult, 4, "(50:63,50:66)"); - assertErrorPosition(diagnosticResult, 5, "(54:66,54:69)"); - assertErrorPosition(diagnosticResult, 6, "(58:77,58:80)"); - assertErrorPosition(diagnosticResult, 7, "(62:76,62:79)"); - assertErrorPosition(diagnosticResult, 8, "(66:76,66:82)"); - assertErrorPosition(diagnosticResult, 9, "(73:45,73:46)"); - assertErrorPosition(diagnosticResult, 10, "(81:43,81:46)"); - assertErrorPosition(diagnosticResult, 11, "(81:61,81:64)"); - assertErrorPosition(diagnosticResult, 12, "(81:79,81:82)"); - assertErrorPosition(diagnosticResult, 13, "(85:77,85:93)"); + assertErrorPosition(diagnosticResult, 1, "(42:86,42:87)"); + assertErrorPosition(diagnosticResult, 2, "(46:57,46:60)"); + assertErrorPosition(diagnosticResult, 3, "(50:63,50:66)"); + assertErrorPosition(diagnosticResult, 4, "(54:66,54:69)"); + assertErrorPosition(diagnosticResult, 5, "(58:77,58:80)"); + assertErrorPosition(diagnosticResult, 6, "(62:76,62:79)"); + assertErrorPosition(diagnosticResult, 7, "(66:76,66:82)"); + assertErrorPosition(diagnosticResult, 8, "(73:45,73:46)"); + assertErrorPosition(diagnosticResult, 9, "(81:43,81:46)"); + assertErrorPosition(diagnosticResult, 10, "(81:61,81:64)"); + assertErrorPosition(diagnosticResult, 11, "(81:79,81:82)"); + assertErrorPosition(diagnosticResult, 12, "(85:77,85:93)"); } @Test diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java index 6eb81f72d8..555781b659 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java @@ -26,7 +26,6 @@ private CompilerPluginTestConstants() {} public static final String HTTP_101 = "HTTP_101"; public static final String HTTP_102 = "HTTP_102"; - public static final String HTTP_103 = "HTTP_103"; public static final String HTTP_104 = "HTTP_104"; public static final String HTTP_105 = "HTTP_105"; public static final String HTTP_106 = "HTTP_106"; From d5dcdbda0a6463ceca3ddede00666ad5fe2ef4f7 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 12 Jun 2024 11:29:13 +0530 Subject: [PATCH 04/47] Update license and docs --- .../compiler/HttpResourceFunctionNode.java | 5 +++++ .../HttpServiceContractResourceValidator.java | 2 ++ .../compiler/HttpServiceObjTypeAnalyzer.java | 5 +++++ .../codeaction/AddBasePathCodeAction.java | 6 +++++ .../codeaction/ChangeBasePathCodeAction.java | 6 +++++ .../codeaction/ImplementServiceContract.java | 5 +++++ .../http/api/HttpServiceFromContract.java | 22 +++++++++++++++++++ 7 files changed, 51 insertions(+) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java index d7c2dc99e9..4387318fb4 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java @@ -31,6 +31,11 @@ import java.util.Objects; import java.util.Optional; +/** + * Represents an HTTP resource function node which abstracts the function definition node and method declaration node. + * + * @since 2.12.0 + */ public class HttpResourceFunctionNode { Node functionNode; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java index e4d01e80ba..e47e0ea935 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java @@ -56,6 +56,8 @@ /** * Validates a ballerina http resource implemented via the service contract type. + * + * @since 2.12.0 */ public final class HttpServiceContractResourceValidator { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java index e4cdea7fa5..b34236dde3 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java @@ -39,6 +39,11 @@ import static io.ballerina.stdlib.http.compiler.Constants.HTTP_SERVICE_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONTRACT_TYPE; +/** + * Validates the HTTP service object type. + * + * @since 2.12.0 + */ public class HttpServiceObjTypeAnalyzer extends HttpServiceValidator { @Override 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 index 0138b085bb..08b23ef231 100644 --- 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 @@ -42,6 +42,12 @@ 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() { 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 index d81fc995d4..c994c7cf0b 100644 --- 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 @@ -42,6 +42,12 @@ 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() { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java index a8a0abb3a8..42c0a4f768 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java @@ -58,6 +58,11 @@ import static io.ballerina.stdlib.http.compiler.codeaction.Constants.LS; import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; +/** + * Represents a code action to implement all the resource methods from the service contract type. + * + * @since 2.12.0 + */ public class ImplementServiceContract implements CodeAction { @Override public List supportedDiagnosticCodes() { 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 98c6874039..5bc2c3374b 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 @@ -1,3 +1,20 @@ +/* + * 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.api; import io.ballerina.runtime.api.types.AnnotatableType; @@ -14,6 +31,11 @@ import static io.ballerina.runtime.api.utils.StringUtils.fromString; import static io.ballerina.stdlib.http.api.HttpUtil.checkConfigAnnotationAvailability; +/** + * Represents an HTTP service built from a service contract type. + * + * @since 2.0.0 + */ public class HttpServiceFromContract extends HttpService { private ReferenceType serviceContractType; From 3063fa44729268f81c97a1ba85c06f0be8e93f4c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 10:51:16 +0530 Subject: [PATCH 05/47] Change package name pattern --- .../http/compiler/codeaction/ImplementServiceContract.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java index 42c0a4f768..5bac5583e3 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java @@ -160,7 +160,7 @@ private String getMethodSignature(ResourceMethodSymbol resourceMethodSymbol) { } private String sanitizePackageNames(String input) { - Pattern pattern = Pattern.compile("(\\w+)/(\\w+:)(\\d+.\\d+.\\d+):"); + Pattern pattern = Pattern.compile("(\\w+)/(\\w+:)(\\d+\\.\\d+\\.\\d+):"); Matcher matcher = pattern.matcher(input); return matcher.replaceAll("$2"); } From 880ee6be1837076c15ee03d1996988f75b95c861 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 12:23:04 +0530 Subject: [PATCH 06/47] Fix code modifier issues --- .../compiler/HttpResourceFunctionNode.java | 20 ++++++++++++++ .../HttpPayloadParamIdentifier.java | 2 +- .../PayloadAnnotationModifierTask.java | 26 ++++++++++--------- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java index 4387318fb4..236863c216 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java @@ -43,6 +43,7 @@ public class HttpResourceFunctionNode { NodeList resourcePath; FunctionSignatureNode functionSignatureNode; IdentifierToken functionName; + int hashCode; public HttpResourceFunctionNode(FunctionDefinitionNode functionDefinitionNode) { functionNode = new FunctionDefinitionNode(functionDefinitionNode.internalNode(), @@ -51,6 +52,7 @@ public HttpResourceFunctionNode(FunctionDefinitionNode functionDefinitionNode) { resourcePath = functionDefinitionNode.relativeResourcePath(); functionSignatureNode = functionDefinitionNode.functionSignature(); functionName = functionDefinitionNode.functionName(); + hashCode = functionDefinitionNode.hashCode(); } public HttpResourceFunctionNode(MethodDeclarationNode methodDeclarationNode) { @@ -60,6 +62,7 @@ public HttpResourceFunctionNode(MethodDeclarationNode methodDeclarationNode) { resourcePath = methodDeclarationNode.relativeResourcePath(); functionSignatureNode = methodDeclarationNode.methodSignature(); functionName = methodDeclarationNode.methodName(); + hashCode = methodDeclarationNode.hashCode(); } public Optional metadata() { @@ -91,4 +94,21 @@ public Optional getFunctionDefinitionNode() { public Optional getSymbol(SemanticModel semanticModel) { return semanticModel.symbol(functionNode); } + + public Node modify(FunctionSignatureNode updatedFunctionNode) { + if (functionNode instanceof FunctionDefinitionNode functionDefNode) { + FunctionDefinitionNode.FunctionDefinitionNodeModifier resourceModifier = functionDefNode.modify(); + resourceModifier.withFunctionSignature(updatedFunctionNode); + return resourceModifier.apply(); + } else { + MethodDeclarationNode.MethodDeclarationNodeModifier resourceModifier = + ((MethodDeclarationNode) functionNode).modify(); + resourceModifier.withMethodSignature(updatedFunctionNode); + return resourceModifier.apply(); + } + } + + public int getResourceIdentifierCode() { + return hashCode; + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java index c7ba2a8f60..40c19e7d08 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java @@ -179,10 +179,10 @@ void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, HttpResourc ServiceContext serviceContext, Map typeSymbols) { Optional resourceMethodSymbolOptional = member.getSymbol(ctx.semanticModel()); - int resourceId = member.hashCode(); if (resourceMethodSymbolOptional.isEmpty()) { return; } + int resourceId = member.getResourceIdentifierCode(); Optional resourceMethodOptional = resourceMethodSymbolOptional.get().getName(); if (resourceMethodOptional.isPresent()) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java index b66f6fc2e0..9de6865246 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java @@ -24,6 +24,7 @@ import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.Node; @@ -46,6 +47,7 @@ import io.ballerina.projects.plugins.ModifierTask; import io.ballerina.projects.plugins.SourceModifierContext; import io.ballerina.stdlib.http.compiler.Constants; +import io.ballerina.stdlib.http.compiler.HttpResourceFunctionNode; import io.ballerina.stdlib.http.compiler.codemodifier.context.DocumentContext; import io.ballerina.stdlib.http.compiler.codemodifier.context.ResourceContext; import io.ballerina.stdlib.http.compiler.codemodifier.context.ServiceContext; @@ -141,19 +143,24 @@ private NodeList updateMemberNodes(NodeList resourceMembers = new ArrayList<>(); for (Node member : members) { - if (member.kind() != SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { + HttpResourceFunctionNode resourceFunctionNode; + if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { + resourceFunctionNode = new HttpResourceFunctionNode((FunctionDefinitionNode) member); + } else if (member.kind() != SyntaxKind.RESOURCE_ACCESSOR_DECLARATION) { + resourceFunctionNode = new HttpResourceFunctionNode((MethodDeclarationNode) member); + } else { resourceMembers.add(member); continue; } - FunctionDefinitionNode resourceNode = (FunctionDefinitionNode) member; - int resourceId = resourceNode.hashCode(); + + int resourceId = member.hashCode(); if (!serviceContext.containsResource(resourceId)) { resourceMembers.add(member); continue; } ResourceContext resourceContext = serviceContext.getResourceContext(resourceId); - FunctionSignatureNode functionSignatureNode = resourceNode.functionSignature(); + FunctionSignatureNode functionSignatureNode = resourceFunctionNode.functionSignature(); SeparatedNodeList parameterNodes = functionSignatureNode.parameters(); List newParameterNodes = new ArrayList<>(); int index = 0; @@ -183,22 +190,17 @@ private NodeList updateMemberNodes(NodeList(newParameterNodes)); signatureModifier.withParameters(separatedNodeList); FunctionSignatureNode updatedFunctionNode = signatureModifier.apply(); - - FunctionDefinitionNode.FunctionDefinitionNodeModifier resourceModifier = resourceNode.modify(); - resourceModifier.withFunctionSignature(updatedFunctionNode); - FunctionDefinitionNode updatedResourceNode = resourceModifier.apply(); + Node updatedResourceNode = resourceFunctionNode.modify(updatedFunctionNode); resourceMembers.add(updatedResourceNode); } NodeList resourceNodeList = AbstractNodeFactory.createNodeList(resourceMembers); - if (memberNode instanceof ServiceDeclarationNode) { - ServiceDeclarationNode serviceNode = (ServiceDeclarationNode) memberNode; + if (memberNode instanceof ServiceDeclarationNode serviceNode) { ServiceDeclarationNode.ServiceDeclarationNodeModifier serviceDeclarationNodeModifier = serviceNode.modify(); ServiceDeclarationNode updatedServiceDeclarationNode = serviceDeclarationNodeModifier.withMembers(resourceNodeList).apply(); updatedMembers.add(updatedServiceDeclarationNode); - } else if (memberNode.kind() == SyntaxKind.CLASS_DEFINITION) { - ClassDefinitionNode classDefinitionNode = (ClassDefinitionNode) memberNode; + } else if (memberNode instanceof ClassDefinitionNode classDefinitionNode) { ClassDefinitionNode.ClassDefinitionNodeModifier classDefinitionNodeModifier = classDefinitionNode.modify(); ClassDefinitionNode updatedClassDefinitionNode = From 9b930194c706c769861c92efa56018d9b575ae81 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 12:23:14 +0530 Subject: [PATCH 07/47] Update minor version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c469d7d23c..e8ad947235 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=2.11.2-SNAPSHOT +version=2.12.0-SNAPSHOT ballerinaLangVersion=2201.9.0 ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 From 3e4fcc9c508fd0e451bf43523139f57fc5d0f6b6 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 12:56:43 +0530 Subject: [PATCH 08/47] Make payload annotation non-constant --- ballerina/http_annotation.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index e625bb0498..e5fabbc3f9 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -89,7 +89,7 @@ public type HttpPayload record {| |}; # The annotation which is used to define the Payload resource signature parameter and return parameter. -public const annotation HttpPayload Payload on parameter, return; +public annotation HttpPayload Payload on parameter, return; # Configures the typing details type of the Caller resource signature parameter. # From 17292f11038701c7cc1c4849fd949a37da0d7f4f Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 12:57:04 +0530 Subject: [PATCH 09/47] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 6 +++--- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index c31f962c74..b85b6b35d5 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" authors = ["Ballerina"] keywords = ["http", "network", "service", "listener", "client"] repository = "https://github.com/ballerina-platform/module-ballerina-http" @@ -16,8 +16,8 @@ graalvmCompatible = true [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" artifactId = "http-native" -version = "2.11.2" -path = "../native/build/libs/http-native-2.11.2-SNAPSHOT.jar" +version = "2.12.0" +path = "../native/build/libs/http-native-2.12.0-SNAPSHOT.jar" [[platform.java17.dependency]] groupId = "io.ballerina.stdlib" diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 3cbad54670..fc0dcc7e77 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -3,4 +3,4 @@ id = "http-compiler-plugin" class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" [[dependency]] -path = "../compiler-plugin/build/libs/http-compiler-plugin-2.11.2-SNAPSHOT.jar" +path = "../compiler-plugin/build/libs/http-compiler-plugin-2.12.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 4def31d650..957b695e23 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -50,7 +50,7 @@ modules = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" dependencies = [ {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "time"} @@ -76,7 +76,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "cache"}, @@ -128,7 +128,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, From e35337b6ea86a0153d187e467afda98f1ba2180d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 13:07:53 +0530 Subject: [PATCH 10/47] [Automated] Update the native jar versions --- ballerina-tests/http-advanced-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-advanced-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-client-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-client-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-dispatching-tests/Ballerina.toml | 6 +++--- .../http-dispatching-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-interceptor-tests/Ballerina.toml | 6 +++--- .../http-interceptor-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-misc-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-misc-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-resiliency-tests/Ballerina.toml | 6 +++--- .../http-resiliency-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-security-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-security-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-service-tests/Ballerina.toml | 6 +++--- ballerina-tests/http-service-tests/Dependencies.toml | 10 +++++----- ballerina-tests/http-test-common/Ballerina.toml | 2 +- ballerina-tests/http-test-common/Dependencies.toml | 2 +- ballerina-tests/http2-tests/Ballerina.toml | 6 +++--- ballerina-tests/http2-tests/Dependencies.toml | 10 +++++----- 20 files changed, 74 insertions(+), 74 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Ballerina.toml b/ballerina-tests/http-advanced-tests/Ballerina.toml index d30b81a7be..2e2f5ab74b 100644 --- a/ballerina-tests/http-advanced-tests/Ballerina.toml +++ b/ballerina-tests/http-advanced-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_advanced_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index c05fe4a76a..6c86d1b6bc 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -72,7 +72,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -105,7 +105,7 @@ modules = [ [[package]] org = "ballerina" name = "http_advanced_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "crypto"}, {org = "ballerina", name = "file"}, @@ -125,7 +125,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -160,7 +160,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-client-tests/Ballerina.toml b/ballerina-tests/http-client-tests/Ballerina.toml index e9e28a2301..f7e2c8b27f 100644 --- a/ballerina-tests/http-client-tests/Ballerina.toml +++ b/ballerina-tests/http-client-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_client_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index d5562e4b98..e45c993117 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -47,7 +47,7 @@ modules = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_client_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-dispatching-tests/Ballerina.toml b/ballerina-tests/http-dispatching-tests/Ballerina.toml index 72d8bd01bb..67e3cf723a 100644 --- a/ballerina-tests/http-dispatching-tests/Ballerina.toml +++ b/ballerina-tests/http-dispatching-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_dispatching_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index ba8f1775c4..8c4b0d41a3 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -47,7 +47,7 @@ modules = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_dispatching_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "constraint"}, {org = "ballerina", name = "http"}, @@ -124,7 +124,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -159,7 +159,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-interceptor-tests/Ballerina.toml b/ballerina-tests/http-interceptor-tests/Ballerina.toml index 35a3d18626..d698448cf6 100644 --- a/ballerina-tests/http-interceptor-tests/Ballerina.toml +++ b/ballerina-tests/http-interceptor-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_interceptor_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index 10a5f703b0..15bc7f993c 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_interceptor_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -115,7 +115,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -147,7 +147,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-misc-tests/Ballerina.toml b/ballerina-tests/http-misc-tests/Ballerina.toml index 207cc0e9d2..80705565f0 100644 --- a/ballerina-tests/http-misc-tests/Ballerina.toml +++ b/ballerina-tests/http-misc-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_misc_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index 22cd357ec5..b800412206 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_misc_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -118,7 +118,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -156,7 +156,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-resiliency-tests/Ballerina.toml b/ballerina-tests/http-resiliency-tests/Ballerina.toml index e9e2e9fd5d..b4c4c0c477 100644 --- a/ballerina-tests/http-resiliency-tests/Ballerina.toml +++ b/ballerina-tests/http-resiliency-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_resiliency_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index e0f7cb7f48..e4084eb584 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -66,7 +66,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -99,7 +99,7 @@ modules = [ [[package]] org = "ballerina" name = "http_resiliency_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "http"}, {org = "ballerina", name = "http_test_common"}, @@ -116,7 +116,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -154,7 +154,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-security-tests/Ballerina.toml b/ballerina-tests/http-security-tests/Ballerina.toml index f465ccf431..f212262908 100644 --- a/ballerina-tests/http-security-tests/Ballerina.toml +++ b/ballerina-tests/http-security-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_security_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 7b439cd343..475cf6350a 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -47,7 +47,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ dependencies = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_security_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "auth"}, {org = "ballerina", name = "http"}, @@ -120,7 +120,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -152,7 +152,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-service-tests/Ballerina.toml b/ballerina-tests/http-service-tests/Ballerina.toml index 0d65071be0..59eb1cf495 100644 --- a/ballerina-tests/http-service-tests/Ballerina.toml +++ b/ballerina-tests/http-service-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http_service_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index 201aa6b1c7..96533099ea 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http_service_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "file"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-test-common/Ballerina.toml b/ballerina-tests/http-test-common/Ballerina.toml index ecdb05e263..e165475d52 100644 --- a/ballerina-tests/http-test-common/Ballerina.toml +++ b/ballerina-tests/http-test-common/Ballerina.toml @@ -1,4 +1,4 @@ [package] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" diff --git a/ballerina-tests/http-test-common/Dependencies.toml b/ballerina-tests/http-test-common/Dependencies.toml index dff80b5dc7..ac897d2d87 100644 --- a/ballerina-tests/http-test-common/Dependencies.toml +++ b/ballerina-tests/http-test-common/Dependencies.toml @@ -10,7 +10,7 @@ distribution-version = "2201.9.0" [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "lang.string"}, {org = "ballerina", name = "mime"}, diff --git a/ballerina-tests/http2-tests/Ballerina.toml b/ballerina-tests/http2-tests/Ballerina.toml index 79f8a8b379..8829d304b3 100644 --- a/ballerina-tests/http2-tests/Ballerina.toml +++ b/ballerina-tests/http2-tests/Ballerina.toml @@ -1,17 +1,17 @@ [package] org = "ballerina" name = "http2_tests" -version = "2.11.2" +version = "2.12.0" [[dependency]] org = "ballerina" name = "http_test_common" repository = "local" -version = "2.11.2" +version = "2.12.0" [platform.java17] graalvmCompatible = true [[platform.java17.dependency]] scope = "testOnly" -path = "../../test-utils/build/libs/http-test-utils-2.11.2-SNAPSHOT.jar" +path = "../../test-utils/build/libs/http-test-utils-2.12.0-SNAPSHOT.jar" diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index 19fad42422..ac05525a2b 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -44,7 +44,7 @@ dependencies = [ [[package]] org = "ballerina" name = "crypto" -version = "2.7.1" +version = "2.7.2" scope = "testOnly" dependencies = [ {org = "ballerina", name = "jballerina.java"}, @@ -69,7 +69,7 @@ modules = [ [[package]] org = "ballerina" name = "http" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "auth"}, @@ -102,7 +102,7 @@ modules = [ [[package]] org = "ballerina" name = "http2_tests" -version = "2.11.2" +version = "2.12.0" dependencies = [ {org = "ballerina", name = "file"}, {org = "ballerina", name = "http"}, @@ -121,7 +121,7 @@ modules = [ [[package]] org = "ballerina" name = "http_test_common" -version = "2.11.2" +version = "2.12.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "lang.string"}, @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, From ccbb692140e5106162cd8aa6d2eac17276713479 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 13:24:50 +0530 Subject: [PATCH 11/47] Update jwt version --- ballerina-tests/http-advanced-tests/Dependencies.toml | 2 +- ballerina-tests/http-client-tests/Dependencies.toml | 2 +- ballerina-tests/http-dispatching-tests/Dependencies.toml | 2 +- ballerina-tests/http-interceptor-tests/Dependencies.toml | 2 +- ballerina-tests/http-misc-tests/Dependencies.toml | 2 +- ballerina-tests/http-resiliency-tests/Dependencies.toml | 2 +- ballerina-tests/http-security-tests/Dependencies.toml | 2 +- ballerina-tests/http-service-tests/Dependencies.toml | 2 +- ballerina-tests/http2-tests/Dependencies.toml | 2 +- ballerina/Dependencies.toml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index 6c86d1b6bc..69c0e154ce 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -160,7 +160,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index e45c993117..1c212697e9 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index 8c4b0d41a3..00b735a6ad 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -159,7 +159,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index 15bc7f993c..d4ab93c642 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -147,7 +147,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index b800412206..0c5d84772a 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -156,7 +156,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index e4084eb584..246afea60e 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -154,7 +154,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 475cf6350a..5dc9eda59d 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -152,7 +152,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index 96533099ea..745986bd92 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index ac05525a2b..506471193d 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 957b695e23..7bfd3e4a76 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -128,7 +128,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, From 1e5a89a690da65dd5019488dcac0847341c91d02 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 14:33:38 +0530 Subject: [PATCH 12/47] Fix compiler test failure --- .../compiler/codemodifier/PayloadAnnotationModifierTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java index 9de6865246..00191e1955 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java @@ -146,7 +146,7 @@ private NodeList updateMemberNodes(NodeList Date: Mon, 17 Jun 2024 14:48:56 +0530 Subject: [PATCH 13/47] Fix service type modifier --- .../codemodifier/ServiceTypeModifierTask.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java index 4cd5f9ac5a..c3411523f8 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java @@ -17,6 +17,7 @@ */ package io.ballerina.stdlib.http.compiler.codemodifier; +import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; @@ -58,6 +59,7 @@ import static io.ballerina.stdlib.http.compiler.Constants.COLON; import static io.ballerina.stdlib.http.compiler.Constants.HTTP; import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONFIG_ANNOTATION; +import static io.ballerina.stdlib.http.compiler.HttpServiceValidator.getServiceContractTypeDesc; /** * {@code ServiceTypeModifierTask} injects the `serviceType` field in the `http:ServiceConfig` annotation. @@ -97,7 +99,8 @@ private void modifyServiceDeclarationsPerDocument(SourceModifierContext modifier Module currentModule) { Document currentDoc = currentModule.document(documentId); ModulePartNode rootNode = currentDoc.syntaxTree().rootNode(); - NodeList newMembers = updateMemberNodes(rootNode.members()); + SemanticModel semanticModel = modifierContext.compilation().getSemanticModel(currentModule.moduleId()); + NodeList newMembers = updateMemberNodes(rootNode.members(), semanticModel); ModulePartNode newModulePart = rootNode.modify(rootNode.imports(), newMembers, rootNode.eofToken()); SyntaxTree updatedSyntaxTree = currentDoc.syntaxTree().modifyWith(newModulePart); TextDocument textDocument = updatedSyntaxTree.textDocument(); @@ -108,11 +111,12 @@ private void modifyServiceDeclarationsPerDocument(SourceModifierContext modifier } } - private NodeList updateMemberNodes(NodeList oldMembers) { + private NodeList updateMemberNodes(NodeList oldMembers, + SemanticModel semanticModel) { List updatedMembers = new ArrayList<>(); for (ModuleMemberDeclarationNode memberNode : oldMembers) { if (memberNode.kind().equals(SyntaxKind.SERVICE_DECLARATION)) { - updatedMembers.add(updateServiceDeclarationNode((ServiceDeclarationNode) memberNode)); + updatedMembers.add(updateServiceDeclarationNode((ServiceDeclarationNode) memberNode, semanticModel)); } else { updatedMembers.add(memberNode); } @@ -120,8 +124,10 @@ private NodeList updateMemberNodes(NodeList serviceTypeDesc = serviceDeclarationNode.typeDescriptor(); + private ServiceDeclarationNode updateServiceDeclarationNode(ServiceDeclarationNode serviceDeclarationNode, + SemanticModel semanticModel) { + Optional serviceTypeDesc = getServiceContractTypeDesc(semanticModel, + serviceDeclarationNode); if (serviceTypeDesc.isEmpty()) { return serviceDeclarationNode; } From cd3d60266784bdbdb7f959223f5e245a3d891b91 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 17:01:03 +0530 Subject: [PATCH 14/47] Refactor base path logic --- ballerina/http_annotation.bal | 15 +- .../stdlib/http/compiler/Constants.java | 4 +- .../http/compiler/HttpCompilerPlugin.java | 6 +- .../http/compiler/HttpDiagnosticCodes.java | 10 +- .../http/compiler/HttpServiceValidator.java | 149 +++--------------- .../codeaction/AddBasePathCodeAction.java | 106 ------------- .../codeaction/ChangeBasePathCodeAction.java | 107 ------------- .../stdlib/http/api/HTTPServicesRegistry.java | 10 +- .../stdlib/http/api/HttpConstants.java | 1 + .../http/api/HttpServiceFromContract.java | 6 + 10 files changed, 42 insertions(+), 372 deletions(-) delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddBasePathCodeAction.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeBasePathCodeAction.java 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()); From c57777a07d86de2db4dde195a9bc7b30ff3a6931 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 17 Jun 2024 17:10:33 +0530 Subject: [PATCH 15/47] Address sonar cloud issues --- .../AddResourceParameterCodeAction.java | 20 ++++----- .../AddResponseCacheConfigCodeAction.java | 19 ++++----- .../AddResponseContentTypeCodeAction.java | 19 ++++----- .../ChangeHeaderParamTypeCodeAction.java | 27 ++++-------- .../ChangeReturnTypeWithCallerCodeAction.java | 20 ++++----- .../compiler/codeaction/CodeActionUtil.java | 35 ++++++++++++++++ .../codeaction/ImplementServiceContract.java | 19 +++------ .../stdlib/http/api/HttpService.java | 42 ++++++++++--------- .../http/api/HttpServiceFromContract.java | 30 ++++--------- 9 files changed, 108 insertions(+), 123 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResourceParameterCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResourceParameterCodeAction.java index 4f1fdf9a3a..8bf6e5f869 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResourceParameterCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResourceParameterCodeAction.java @@ -24,7 +24,6 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; 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; @@ -43,7 +42,8 @@ import java.util.List; import java.util.Optional; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getCodeActionInfoWithLocation; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getLineRangeFromLocationKey; /** * Abstract implementation of code action to add a parameter to the resource signature. @@ -71,25 +71,19 @@ public Optional codeActionInfo(CodeActionContext context) { cursorPosition.get())) { return Optional.empty(); } - CodeActionArgument arg = CodeActionArgument.from(NODE_LOCATION_KEY, node.lineRange()); - CodeActionInfo info = CodeActionInfo.from(String.format("Add %s parameter", paramKind()), List.of(arg)); - return Optional.of(info); + return getCodeActionInfoWithLocation(node, String.format("Add %s parameter", paramKind())); } @Override public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - for (CodeActionArgument arg : context.arguments()) { - if (NODE_LOCATION_KEY.equals(arg.key())) { - lineRange = arg.valueAs(LineRange.class); - } - } - if (lineRange == null) { + Optional lineRange = getLineRangeFromLocationKey(context); + + if (lineRange.isEmpty()) { return Collections.emptyList(); } SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); - NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange); + NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange.get()); if (!node.kind().equals(SyntaxKind.FUNCTION_SIGNATURE)) { return Collections.emptyList(); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java index ccde54a8a5..b55c630657 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java @@ -22,7 +22,6 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; 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; @@ -39,7 +38,8 @@ import java.util.List; import java.util.Optional; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getCodeActionInfoWithLocation; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getLineRangeFromLocationKey; /** * CodeAction to add response cache configuration. @@ -58,24 +58,19 @@ public Optional codeActionInfo(CodeActionContext context) { return Optional.empty(); } - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); - return Optional.of(CodeActionInfo.from("Add response cache configuration", List.of(locationArg))); + return getCodeActionInfoWithLocation(node, "Add response cache configuration"); } @Override public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - for (CodeActionArgument arg : context.arguments()) { - if (NODE_LOCATION_KEY.equals(arg.key())) { - lineRange = arg.valueAs(LineRange.class); - } - } - if (lineRange == null) { + Optional lineRange = getLineRangeFromLocationKey(context); + + if (lineRange.isEmpty()) { return Collections.emptyList(); } SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); - NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange); + NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange.get()); String cacheConfig = "@http:Cache "; int start = node.position(); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java index 13d8532be8..abd878b306 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java @@ -22,7 +22,6 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; 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; @@ -39,7 +38,8 @@ import java.util.List; import java.util.Optional; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getCodeActionInfoWithLocation; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getLineRangeFromLocationKey; /** * CodeAction to add response content-type. @@ -58,24 +58,19 @@ public Optional codeActionInfo(CodeActionContext context) { return Optional.empty(); } - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); - return Optional.of(CodeActionInfo.from("Add response content-type", List.of(locationArg))); + return getCodeActionInfoWithLocation(node, "Add response content-type"); } @Override public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - for (CodeActionArgument arg : context.arguments()) { - if (NODE_LOCATION_KEY.equals(arg.key())) { - lineRange = arg.valueAs(LineRange.class); - } - } - if (lineRange == null) { + Optional lineRange = getLineRangeFromLocationKey(context); + + if (lineRange.isEmpty()) { return Collections.emptyList(); } SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); - NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange); + NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange.get()); String mediaTypedPayload = "@http:Payload{mediaType: \"\"} "; int start = node.position(); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java index 132746efb0..47cbaa5872 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java @@ -25,7 +25,6 @@ import io.ballerina.compiler.syntax.tree.RestParameterNode; 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; @@ -44,7 +43,8 @@ import java.util.List; import java.util.Optional; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getCodeActionInfoWithLocation; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getLineRangeFromLocationKey; /** * Abstract implementation of code action to change a resource header param's type. @@ -66,38 +66,27 @@ public Optional codeActionInfo(CodeActionContext context) { DiagnosticProperty diagnosticProperty = properties.get(0); if (!(diagnosticProperty instanceof BSymbolicProperty) || - !(diagnosticProperty.value() instanceof ParameterSymbol)) { + !(diagnosticProperty.value() instanceof ParameterSymbol parameterSymbol)) { return Optional.empty(); } - ParameterSymbol parameterSymbol = (ParameterSymbol) diagnosticProperty.value(); Optional nonTerminalNode = parameterSymbol.getLocation() .flatMap(location -> Optional.ofNullable(CodeActionUtil.findNode(syntaxTree, parameterSymbol))); - if (nonTerminalNode.isEmpty()) { - return Optional.empty(); - } + return nonTerminalNode.flatMap(terminalNode -> getCodeActionInfoWithLocation(terminalNode, + String.format("Change header param to '%s'", headerParamType()))); - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, - nonTerminalNode.get().location().lineRange()); - return Optional.of(CodeActionInfo.from(String.format("Change header param to '%s'", headerParamType()), - List.of(locationArg))); } @Override public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - for (CodeActionArgument argument : context.arguments()) { - if (NODE_LOCATION_KEY.equals(argument.key())) { - lineRange = argument.valueAs(LineRange.class); - } - } + Optional lineRange = getLineRangeFromLocationKey(context); - if (lineRange == null) { + if (lineRange.isEmpty()) { return Collections.emptyList(); } SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); - NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange); + NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange.get()); TextRange typeNodeTextRange; switch (node.kind()) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java index 776583b777..98ef874ed0 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java @@ -20,7 +20,6 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; 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; @@ -37,7 +36,8 @@ import java.util.List; import java.util.Optional; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getCodeActionInfoWithLocation; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getLineRangeFromLocationKey; /** * Codeaction to change the return type to
error?
if the resource function has
http:Caller
as a @@ -58,28 +58,22 @@ public Optional codeActionInfo(CodeActionContext context) { return Optional.empty(); } - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); - return Optional.of(CodeActionInfo.from("Change return type to 'error?'", List.of(locationArg))); + return getCodeActionInfoWithLocation(node, "Change return type to 'error?'"); } @Override public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - for (CodeActionArgument argument : context.arguments()) { - if (NODE_LOCATION_KEY.equals(argument.key())) { - lineRange = argument.valueAs(LineRange.class); - } - } + Optional lineRange = getLineRangeFromLocationKey(context); - if (lineRange == null) { + if (lineRange.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()); + int start = textDocument.textPositionFrom(lineRange.get().startLine()); + int end = textDocument.textPositionFrom(lineRange.get().endLine()); List textEdits = new ArrayList<>(); textEdits.add(TextEdit.from(TextRange.from(start, end - start), "error?")); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/CodeActionUtil.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/CodeActionUtil.java index 74caf905f4..a996b1108e 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/CodeActionUtil.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/CodeActionUtil.java @@ -20,11 +20,19 @@ import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.NonTerminalNode; import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.projects.plugins.codeaction.CodeActionArgument; +import io.ballerina.projects.plugins.codeaction.CodeActionExecutionContext; +import io.ballerina.projects.plugins.codeaction.CodeActionInfo; import io.ballerina.tools.text.LinePosition; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextRange; +import java.util.List; +import java.util.Optional; + +import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; + /** * Utilities for code actions. */ @@ -90,4 +98,31 @@ public static boolean isWithinRange(LineRange lineRange, LinePosition pos) { pos.line() == sLine && pos.offset() >= sCol )); } + + /** + * Get code action info with location. + * + * @param node Node + * @param title Title + * @return Code action info with location + */ + public static Optional getCodeActionInfoWithLocation(NonTerminalNode node, String title) { + CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); + return Optional.of(CodeActionInfo.from(title, List.of(locationArg))); + } + + /** + * Get line range from location key. + * + * @param context Code action execution context + * @return Line range + */ + public static Optional getLineRangeFromLocationKey(CodeActionExecutionContext context) { + for (CodeActionArgument argument : context.arguments()) { + if (NODE_LOCATION_KEY.equals(argument.key())) { + return Optional.of(argument.valueAs(LineRange.class)); + } + } + return Optional.empty(); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java index 5bac5583e3..4a9040f9fc 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java @@ -33,7 +33,6 @@ import io.ballerina.compiler.syntax.tree.SyntaxTree; import io.ballerina.compiler.syntax.tree.TypeDescriptorNode; 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; @@ -55,8 +54,9 @@ import static io.ballerina.stdlib.http.compiler.HttpServiceContractResourceValidator.constructResourcePathName; import static io.ballerina.stdlib.http.compiler.HttpServiceValidator.getServiceContractTypeDesc; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getCodeActionInfoWithLocation; +import static io.ballerina.stdlib.http.compiler.codeaction.CodeActionUtil.getLineRangeFromLocationKey; import static io.ballerina.stdlib.http.compiler.codeaction.Constants.LS; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; /** * Represents a code action to implement all the resource methods from the service contract type. @@ -77,27 +77,20 @@ public Optional codeActionInfo(CodeActionContext context) { return Optional.empty(); } - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); - return Optional.of(CodeActionInfo.from("Implement all the resource methods from service contract", - List.of(locationArg))); + return getCodeActionInfoWithLocation(node, "Implement all the resource methods from service contract"); } @Override public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - for (CodeActionArgument argument : context.arguments()) { - if (NODE_LOCATION_KEY.equals(argument.key())) { - lineRange = argument.valueAs(LineRange.class); - } - } + Optional lineRange = getLineRangeFromLocationKey(context); - if (lineRange == null) { + if (lineRange.isEmpty()) { return Collections.emptyList(); } SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); SemanticModel semanticModel = context.currentSemanticModel(); - NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange); + NonTerminalNode node = CodeActionUtil.findNode(syntaxTree, lineRange.get()); if (!node.kind().equals(SyntaxKind.SERVICE_DECLARATION)) { return Collections.emptyList(); } diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index ec249c14ca..5270c8105f 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -71,14 +71,12 @@ public class HttpService implements Service { private static final Logger log = LoggerFactory.getLogger(HttpService.class); - protected static final BString BASE_PATH_FIELD = fromString("basePath"); - protected static final BString CORS_FIELD = fromString("cors"); - private static final BString VERSIONING_FIELD = fromString("versioning"); - protected static final BString HOST_FIELD = fromString("host"); - protected static final BString OPENAPI_DEF_FIELD = fromString("openApiDefinition"); - protected static final BString MEDIA_TYPE_SUBTYPE_PREFIX = fromString("mediaTypeSubtypePrefix"); - protected static final BString TREAT_NILABLE_AS_OPTIONAL = fromString("treatNilableAsOptional"); - protected static final BString DATA_VALIDATION = fromString("validation"); + private static final BString CORS_FIELD = fromString("cors"); + private static final BString HOST_FIELD = fromString("host"); + private static final BString OPENAPI_DEF_FIELD = fromString("openApiDefinition"); + private static final BString MEDIA_TYPE_SUBTYPE_PREFIX = fromString("mediaTypeSubtypePrefix"); + private static final BString TREAT_NILABLE_AS_OPTIONAL = fromString("treatNilableAsOptional"); + private static final BString DATA_VALIDATION = fromString("validation"); private BObject balService; private List resources; @@ -239,25 +237,29 @@ public URITemplate getUriTemplate() throws URITempl public static HttpService buildHttpService(BObject service, String basePath) { HttpService httpService = new HttpService(service, basePath); BMap serviceConfig = getHttpServiceConfigAnnotation(service); + httpService.populateServiceConfig(serviceConfig); + return httpService; + } + + protected void populateServiceConfig(BMap serviceConfig) { if (checkConfigAnnotationAvailability(serviceConfig)) { - httpService.setCompressionConfig( + this.setCompressionConfig( (BMap) serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_COMPRESSION)); - httpService.setChunkingConfig(serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_CHUNKING).toString()); - httpService.setCorsHeaders(CorsHeaders.buildCorsHeaders(serviceConfig.getMapValue(CORS_FIELD))); - httpService.setHostName(serviceConfig.getStringValue(HOST_FIELD).getValue().trim()); - httpService.setIntrospectionPayload(serviceConfig.getArrayValue(OPENAPI_DEF_FIELD).getByteArray()); + this.setChunkingConfig(serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_CHUNKING).toString()); + this.setCorsHeaders(CorsHeaders.buildCorsHeaders(serviceConfig.getMapValue(CORS_FIELD))); + this.setHostName(serviceConfig.getStringValue(HOST_FIELD).getValue().trim()); + this.setIntrospectionPayload(serviceConfig.getArrayValue(OPENAPI_DEF_FIELD).getByteArray()); if (serviceConfig.containsKey(MEDIA_TYPE_SUBTYPE_PREFIX)) { - httpService.setMediaTypeSubtypePrefix(serviceConfig.getStringValue(MEDIA_TYPE_SUBTYPE_PREFIX) + this.setMediaTypeSubtypePrefix(serviceConfig.getStringValue(MEDIA_TYPE_SUBTYPE_PREFIX) .getValue().trim()); } - httpService.setTreatNilableAsOptional(serviceConfig.getBooleanValue(TREAT_NILABLE_AS_OPTIONAL)); - httpService.setConstraintValidation(serviceConfig.getBooleanValue(DATA_VALIDATION)); + this.setTreatNilableAsOptional(serviceConfig.getBooleanValue(TREAT_NILABLE_AS_OPTIONAL)); + this.setConstraintValidation(serviceConfig.getBooleanValue(DATA_VALIDATION)); } else { - httpService.setHostName(HttpConstants.DEFAULT_HOST); + this.setHostName(HttpConstants.DEFAULT_HOST); } - processResources(httpService); - httpService.setAllAllowedMethods(DispatcherUtil.getAllResourceMethods(httpService)); - return httpService; + processResources(this); + this.setAllAllowedMethods(DispatcherUtil.getAllResourceMethods(this)); } protected static void processResources(HttpService httpService) { 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 def4b288c7..e687cf069f 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 @@ -24,9 +24,7 @@ import io.ballerina.runtime.api.utils.TypeUtils; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; -import io.ballerina.runtime.api.values.BString; import io.ballerina.stdlib.http.api.nativeimpl.ModuleUtils; -import io.ballerina.stdlib.http.uri.DispatcherUtil; import java.util.Objects; @@ -40,7 +38,7 @@ */ public class HttpServiceFromContract extends HttpService { - private ReferenceType serviceContractType; + private final ReferenceType serviceContractType; protected HttpServiceFromContract(BObject service, String basePath, ReferenceType httpServiceContractType) { super(service, basePath); @@ -51,29 +49,19 @@ public static HttpService buildHttpService(BObject service, String basePath, ReferenceType serviceContractType) { HttpService httpService = new HttpServiceFromContract(service, basePath, serviceContractType); BMap serviceConfig = getHttpServiceConfigAnnotation(serviceContractType); + httpService.populateServiceConfig(serviceConfig); + return httpService; + } + + @Override + protected void populateServiceConfig(BMap serviceConfig) { 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()); - httpService.setCorsHeaders(CorsHeaders.buildCorsHeaders(serviceConfig.getMapValue(CORS_FIELD))); - httpService.setHostName(serviceConfig.getStringValue(HOST_FIELD).getValue().trim()); - httpService.setIntrospectionPayload(serviceConfig.getArrayValue(OPENAPI_DEF_FIELD).getByteArray()); - if (serviceConfig.containsKey(MEDIA_TYPE_SUBTYPE_PREFIX)) { - httpService.setMediaTypeSubtypePrefix(serviceConfig.getStringValue(MEDIA_TYPE_SUBTYPE_PREFIX) - .getValue().trim()); + this.setBasePath(basePathFromAnnotation.toString()); } - httpService.setTreatNilableAsOptional(serviceConfig.getBooleanValue(TREAT_NILABLE_AS_OPTIONAL)); - httpService.setConstraintValidation(serviceConfig.getBooleanValue(DATA_VALIDATION)); - } else { - httpService.setHostName(HttpConstants.DEFAULT_HOST); } - processResources(httpService); - httpService.setAllAllowedMethods(DispatcherUtil.getAllResourceMethods(httpService)); - return httpService; + super.populateServiceConfig(serviceConfig); } public static BMap getHttpServiceConfigAnnotation(ReferenceType serviceContractType) { From 90fa07d8b3fbbb08dfff27da4d30fdc98c30c006 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Tue, 18 Jun 2024 07:45:44 +0530 Subject: [PATCH 16/47] Refactor resource function node --- .../HttpInterceptorResourceValidator.java | 2 +- .../compiler/HttpResourceFunctionNode.java | 114 ------------------ .../http/compiler/HttpResourceValidator.java | 16 +-- .../http/compiler/ResourceFunction.java | 56 +++++++++ .../compiler/ResourceFunctionDeclaration.java | 90 ++++++++++++++ .../compiler/ResourceFunctionDefinition.java | 90 ++++++++++++++ .../HttpPayloadParamIdentifier.java | 12 +- .../PayloadAnnotationModifierTask.java | 12 +- 8 files changed, 259 insertions(+), 133 deletions(-) delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunction.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDeclaration.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDefinition.java diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java index f7a83a077f..f7e9a62be8 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java @@ -44,7 +44,7 @@ public static void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefin if (isRequestErrorInterceptor(type)) { extractAndValidateMethodAndPath(ctx, member); } - HttpResourceFunctionNode functionNode = new HttpResourceFunctionNode(member); + ResourceFunction functionNode = new ResourceFunctionDefinition(member); HttpResourceValidator.extractInputParamTypeAndValidate(ctx, functionNode, isRequestErrorInterceptor(type), typeSymbols); HttpCompilerPluginUtil.extractInterceptorReturnTypeAndValidate(ctx, typeSymbols, member, diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java deleted file mode 100644 index 236863c216..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceFunctionNode.java +++ /dev/null @@ -1,114 +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; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.api.symbols.Symbol; -import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; -import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; -import io.ballerina.compiler.syntax.tree.IdentifierToken; -import io.ballerina.compiler.syntax.tree.MetadataNode; -import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; -import io.ballerina.compiler.syntax.tree.Node; -import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.tools.diagnostics.Location; - -import java.util.Objects; -import java.util.Optional; - -/** - * Represents an HTTP resource function node which abstracts the function definition node and method declaration node. - * - * @since 2.12.0 - */ -public class HttpResourceFunctionNode { - - Node functionNode; - MetadataNode metadata = null; - NodeList resourcePath; - FunctionSignatureNode functionSignatureNode; - IdentifierToken functionName; - int hashCode; - - public HttpResourceFunctionNode(FunctionDefinitionNode functionDefinitionNode) { - functionNode = new FunctionDefinitionNode(functionDefinitionNode.internalNode(), - functionDefinitionNode.position(), functionDefinitionNode.parent()); - functionDefinitionNode.metadata().ifPresent(metadataNode -> metadata = metadataNode); - resourcePath = functionDefinitionNode.relativeResourcePath(); - functionSignatureNode = functionDefinitionNode.functionSignature(); - functionName = functionDefinitionNode.functionName(); - hashCode = functionDefinitionNode.hashCode(); - } - - public HttpResourceFunctionNode(MethodDeclarationNode methodDeclarationNode) { - functionNode = new MethodDeclarationNode(methodDeclarationNode.internalNode(), - methodDeclarationNode.position(), methodDeclarationNode.parent()); - methodDeclarationNode.metadata().ifPresent(metadataNode -> metadata = metadataNode); - resourcePath = methodDeclarationNode.relativeResourcePath(); - functionSignatureNode = methodDeclarationNode.methodSignature(); - functionName = methodDeclarationNode.methodName(); - hashCode = methodDeclarationNode.hashCode(); - } - - public Optional metadata() { - return Objects.isNull(metadata) ? Optional.empty() : Optional.of(metadata); - } - - public NodeList relativeResourcePath() { - return resourcePath; - } - - public FunctionSignatureNode functionSignature() { - return new FunctionSignatureNode(functionSignatureNode.internalNode(), functionSignatureNode.position(), - functionSignatureNode.parent()); - } - - public IdentifierToken functionName() { - return new IdentifierToken(functionName.internalNode(), functionName.position(), functionName.parent()); - } - - public Location location() { - return functionNode.location(); - } - - public Optional getFunctionDefinitionNode() { - return functionNode instanceof FunctionDefinitionNode ? - Optional.of((FunctionDefinitionNode) functionNode) : Optional.empty(); - } - - public Optional getSymbol(SemanticModel semanticModel) { - return semanticModel.symbol(functionNode); - } - - public Node modify(FunctionSignatureNode updatedFunctionNode) { - if (functionNode instanceof FunctionDefinitionNode functionDefNode) { - FunctionDefinitionNode.FunctionDefinitionNodeModifier resourceModifier = functionDefNode.modify(); - resourceModifier.withFunctionSignature(updatedFunctionNode); - return resourceModifier.apply(); - } else { - MethodDeclarationNode.MethodDeclarationNodeModifier resourceModifier = - ((MethodDeclarationNode) functionNode).modify(); - resourceModifier.withMethodSignature(updatedFunctionNode); - return resourceModifier.apply(); - } - } - - public int getResourceIdentifierCode() { - return hashCode; - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java index 349d8553a5..67c2e45b07 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java @@ -135,7 +135,7 @@ private HttpResourceValidator() {} static void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, LinksMetaData linksMetaData, Map typeSymbols) { - HttpResourceFunctionNode functionNode = new HttpResourceFunctionNode(member); + ResourceFunction functionNode = new ResourceFunctionDefinition(member); extractResourceAnnotationAndValidate(ctx, functionNode, linksMetaData); extractInputParamTypeAndValidate(ctx, functionNode, false, typeSymbols); extractReturnTypeAndValidate(ctx, functionNode, typeSymbols); @@ -144,14 +144,14 @@ static void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNo static void validateResource(SyntaxNodeAnalysisContext ctx, MethodDeclarationNode member, LinksMetaData linksMetaData, Map typeSymbols) { - HttpResourceFunctionNode functionNode = new HttpResourceFunctionNode(member); + ResourceFunction functionNode = new ResourceFunctionDeclaration(member); extractResourceAnnotationAndValidate(ctx, functionNode, linksMetaData); extractInputParamTypeAndValidate(ctx, functionNode, false, typeSymbols); extractReturnTypeAndValidate(ctx, functionNode, typeSymbols); } private static void extractResourceAnnotationAndValidate(SyntaxNodeAnalysisContext ctx, - HttpResourceFunctionNode member, + ResourceFunction member, LinksMetaData linksMetaData) { Optional metadataNodeOptional = member.metadata(); if (metadataNodeOptional.isEmpty()) { @@ -170,7 +170,7 @@ private static void extractResourceAnnotationAndValidate(SyntaxNodeAnalysisConte } } - private static void validateLinksInResourceConfig(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, + private static void validateLinksInResourceConfig(SyntaxNodeAnalysisContext ctx, ResourceFunction member, AnnotationNode annotation, LinksMetaData linksMetaData) { Optional optionalMapping = annotation.annotValue(); if (optionalMapping.isEmpty()) { @@ -193,7 +193,7 @@ private static void validateLinksInResourceConfig(SyntaxNodeAnalysisContext ctx, } } - private static void validateResourceNameField(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, + private static void validateResourceNameField(SyntaxNodeAnalysisContext ctx, ResourceFunction member, SpecificFieldNode field, LinksMetaData linksMetaData) { Optional fieldValueExpression = field.valueExpr(); if (fieldValueExpression.isEmpty()) { @@ -214,7 +214,7 @@ private static void validateResourceNameField(SyntaxNodeAnalysisContext ctx, Htt } } - private static String getRelativePathFromFunctionNode(HttpResourceFunctionNode member) { + private static String getRelativePathFromFunctionNode(ResourceFunction member) { NodeList nodes = member.relativeResourcePath(); String path = EMPTY; for (Node node : nodes) { @@ -290,7 +290,7 @@ private static void populateLinkedToResources(SyntaxNodeAnalysisContext ctx, } } - public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, + public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, ResourceFunction member, boolean isErrorInterceptor, Map typeSymbols) { boolean callerPresent = false; @@ -778,7 +778,7 @@ private static List getRespondParamNode(SyntaxNodeAnalys return respondNodeVisitor.getRespondStatementNodes(); } - private static void extractReturnTypeAndValidate(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, + private static void extractReturnTypeAndValidate(SyntaxNodeAnalysisContext ctx, ResourceFunction member, Map typeSymbols) { Optional returnTypeDescriptorNode = member.functionSignature().returnTypeDesc(); if (returnTypeDescriptorNode.isEmpty()) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunction.java new file mode 100644 index 0000000000..445d9ec73e --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunction.java @@ -0,0 +1,56 @@ +/* + * 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; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; +import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.tools.diagnostics.Location; + +import java.util.Optional; + +/** + * Represents an HTTP resource function node interface. + * + * @since 2.12.0 + */ +public interface ResourceFunction { + + Optional metadata(); + + NodeList relativeResourcePath(); + + FunctionSignatureNode functionSignature(); + + IdentifierToken functionName(); + + Location location(); + + Optional getFunctionDefinitionNode(); + + Optional getSymbol(SemanticModel semanticModel); + + Node modifyWithSignature(FunctionSignatureNode updatedFunctionNode); + + int getResourceIdentifierCode(); +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDeclaration.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDeclaration.java new file mode 100644 index 0000000000..f60f307a97 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDeclaration.java @@ -0,0 +1,90 @@ +/* + * 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; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; +import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.tools.diagnostics.Location; + +import java.util.Optional; + +/** + * Represents an HTTP resource method declaration node adapter. + * + * @since 2.12.0 + */ +public class ResourceFunctionDeclaration implements ResourceFunction { + + MethodDeclarationNode methodDeclarationNode; + FunctionSignatureNode functionSignatureNode; + IdentifierToken functionName; + int hashCode; + + public ResourceFunctionDeclaration(MethodDeclarationNode methodNode) { + methodDeclarationNode = new MethodDeclarationNode(methodNode.internalNode(), + methodNode.position(), methodNode.parent()); + functionSignatureNode = methodNode.methodSignature(); + functionName = methodNode.methodName(); + hashCode = methodNode.hashCode(); + } + + public Optional metadata() { + return methodDeclarationNode.metadata(); + } + + public NodeList relativeResourcePath() { + return methodDeclarationNode.relativeResourcePath(); + } + + public FunctionSignatureNode functionSignature() { + return new FunctionSignatureNode(functionSignatureNode.internalNode(), functionSignatureNode.position(), + functionSignatureNode.parent()); + } + + public IdentifierToken functionName() { + return new IdentifierToken(functionName.internalNode(), functionName.position(), functionName.parent()); + } + + public Location location() { + return methodDeclarationNode.location(); + } + + public Optional getFunctionDefinitionNode() { + return Optional.empty(); + } + + public Optional getSymbol(SemanticModel semanticModel) { + return semanticModel.symbol(methodDeclarationNode); + } + + public Node modifyWithSignature(FunctionSignatureNode updatedFunctionNode) { + MethodDeclarationNode.MethodDeclarationNodeModifier resourceModifier = methodDeclarationNode.modify(); + resourceModifier.withMethodSignature(updatedFunctionNode); + return resourceModifier.apply(); + } + + public int getResourceIdentifierCode() { + return hashCode; + }} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDefinition.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDefinition.java new file mode 100644 index 0000000000..4b05ed11de --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ResourceFunctionDefinition.java @@ -0,0 +1,90 @@ +/* + * 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; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.FunctionSignatureNode; +import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.tools.diagnostics.Location; + +import java.util.Optional; + +/** + * Represents an HTTP resource function definition node adapter. + * + * @since 2.12.0 + */ +public class ResourceFunctionDefinition implements ResourceFunction { + + FunctionDefinitionNode functionDefinitionNode; + FunctionSignatureNode functionSignatureNode; + IdentifierToken functionName; + int hashCode; + + public ResourceFunctionDefinition(FunctionDefinitionNode functionNode) { + functionDefinitionNode = new FunctionDefinitionNode(functionNode.internalNode(), + functionNode.position(), functionNode.parent()); + functionSignatureNode = functionNode.functionSignature(); + functionName = functionNode.functionName(); + hashCode = functionNode.hashCode(); + } + + public Optional metadata() { + return functionDefinitionNode.metadata(); + } + + public NodeList relativeResourcePath() { + return functionDefinitionNode.relativeResourcePath(); + } + + public FunctionSignatureNode functionSignature() { + return new FunctionSignatureNode(functionSignatureNode.internalNode(), functionSignatureNode.position(), + functionSignatureNode.parent()); + } + + public IdentifierToken functionName() { + return new IdentifierToken(functionName.internalNode(), functionName.position(), functionName.parent()); + } + + public Location location() { + return functionDefinitionNode.location(); + } + + public Optional getFunctionDefinitionNode() { + return Optional.of(functionDefinitionNode); + } + + public Optional getSymbol(SemanticModel semanticModel) { + return semanticModel.symbol(functionDefinitionNode); + } + + public Node modifyWithSignature(FunctionSignatureNode updatedFunctionNode) { + FunctionDefinitionNode.FunctionDefinitionNodeModifier resourceModifier = functionDefinitionNode.modify(); + resourceModifier.withFunctionSignature(updatedFunctionNode); + return resourceModifier.apply(); + } + + public int getResourceIdentifierCode() { + return hashCode; + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java index 40c19e7d08..331267a6f2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java @@ -39,9 +39,11 @@ import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.stdlib.http.compiler.Constants; import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; -import io.ballerina.stdlib.http.compiler.HttpResourceFunctionNode; import io.ballerina.stdlib.http.compiler.HttpResourceValidator; import io.ballerina.stdlib.http.compiler.HttpServiceValidator; +import io.ballerina.stdlib.http.compiler.ResourceFunction; +import io.ballerina.stdlib.http.compiler.ResourceFunctionDeclaration; +import io.ballerina.stdlib.http.compiler.ResourceFunctionDefinition; import io.ballerina.stdlib.http.compiler.codemodifier.context.DocumentContext; import io.ballerina.stdlib.http.compiler.codemodifier.context.ParamAvailability; import io.ballerina.stdlib.http.compiler.codemodifier.context.ParamData; @@ -128,11 +130,11 @@ private void validateResources(SyntaxNodeAnalysisContext syntaxNodeAnalysisConte for (Node member : members) { if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { validateResource(syntaxNodeAnalysisContext, - new HttpResourceFunctionNode((FunctionDefinitionNode) member), serviceContext, + new ResourceFunctionDefinition((FunctionDefinitionNode) member), serviceContext, typeSymbols); } else if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DECLARATION) { validateResource(syntaxNodeAnalysisContext, - new HttpResourceFunctionNode((MethodDeclarationNode) member), serviceContext, + new ResourceFunctionDeclaration((MethodDeclarationNode) member), serviceContext, typeSymbols); } } @@ -170,12 +172,12 @@ private void validateClassDefinition(SyntaxNodeAnalysisContext syntaxNodeAnalysi } } - void validateResource(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, ServiceContext serviceContext, + void validateResource(SyntaxNodeAnalysisContext ctx, ResourceFunction member, ServiceContext serviceContext, Map typeSymbols) { extractInputParamTypeAndValidate(ctx, member, serviceContext, typeSymbols); } - void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, HttpResourceFunctionNode member, + void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, ResourceFunction member, ServiceContext serviceContext, Map typeSymbols) { Optional resourceMethodSymbolOptional = member.getSymbol(ctx.semanticModel()); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java index 00191e1955..fd87e86e07 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java @@ -47,7 +47,9 @@ import io.ballerina.projects.plugins.ModifierTask; import io.ballerina.projects.plugins.SourceModifierContext; import io.ballerina.stdlib.http.compiler.Constants; -import io.ballerina.stdlib.http.compiler.HttpResourceFunctionNode; +import io.ballerina.stdlib.http.compiler.ResourceFunction; +import io.ballerina.stdlib.http.compiler.ResourceFunctionDeclaration; +import io.ballerina.stdlib.http.compiler.ResourceFunctionDefinition; import io.ballerina.stdlib.http.compiler.codemodifier.context.DocumentContext; import io.ballerina.stdlib.http.compiler.codemodifier.context.ResourceContext; import io.ballerina.stdlib.http.compiler.codemodifier.context.ServiceContext; @@ -143,11 +145,11 @@ private NodeList updateMemberNodes(NodeList resourceMembers = new ArrayList<>(); for (Node member : members) { - HttpResourceFunctionNode resourceFunctionNode; + ResourceFunction resourceFunctionNode; if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { - resourceFunctionNode = new HttpResourceFunctionNode((FunctionDefinitionNode) member); + resourceFunctionNode = new ResourceFunctionDefinition((FunctionDefinitionNode) member); } else if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DECLARATION) { - resourceFunctionNode = new HttpResourceFunctionNode((MethodDeclarationNode) member); + resourceFunctionNode = new ResourceFunctionDeclaration((MethodDeclarationNode) member); } else { resourceMembers.add(member); continue; @@ -190,7 +192,7 @@ private NodeList updateMemberNodes(NodeList(newParameterNodes)); signatureModifier.withParameters(separatedNodeList); FunctionSignatureNode updatedFunctionNode = signatureModifier.apply(); - Node updatedResourceNode = resourceFunctionNode.modify(updatedFunctionNode); + Node updatedResourceNode = resourceFunctionNode.modifyWithSignature(updatedFunctionNode); resourceMembers.add(updatedResourceNode); } NodeList resourceNodeList = AbstractNodeFactory.createNodeList(resourceMembers); From 34b65644d289cf13b8549367dfb0ed85613b9a90 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 24 Jun 2024 12:51:10 +0530 Subject: [PATCH 17/47] Move OpenAPI embedding task from openapi-tool --- compiler-plugin/build.gradle | 4 + compiler-plugin/spotbugs-exclude.xml | 34 ++- .../http/compiler/HttpCompilerPluginUtil.java | 18 +- ...agnosticCodes.java => HttpDiagnostic.java} | 8 +- .../HttpInterceptorResourceValidator.java | 8 +- .../HttpInterceptorServiceValidator.java | 32 +-- .../http/compiler/HttpResourceValidator.java | 90 +++--- .../HttpServiceContractResourceValidator.java | 6 +- .../http/compiler/HttpServiceValidator.java | 24 +- .../AddHeaderParameterCodeAction.java | 4 +- .../AddInterceptorRemoteMethodCodeAction.java | 4 +- ...ddInterceptorResourceMethodCodeAction.java | 4 +- .../AddPayloadParameterCodeAction.java | 4 +- .../AddResponseCacheConfigCodeAction.java | 4 +- .../AddResponseContentTypeCodeAction.java | 4 +- .../ChangeHeaderParamTypeCodeAction.java | 4 +- .../ChangeReturnTypeWithCallerCodeAction.java | 4 +- .../codeaction/ImplementServiceContract.java | 4 +- .../codemodifier/HttpServiceModifier.java | 9 +- .../compiler/codemodifier/oas/Constants.java | 39 +++ .../codemodifier/oas/DocGenerationUtils.java | 36 +++ .../oas/HttpServiceAnalysisTask.java | 97 +++++++ .../oas/OpenApiInfoUpdaterTask.java | 262 ++++++++++++++++++ .../oas/context/OpenApiDocContext.java | 83 ++++++ .../oas/context/OpenApiDocContextHandler.java | 91 ++++++ .../context/ServiceNodeAnalysisContext.java | 87 ++++++ .../oas/gen/AbstractOpenApiDocGenerator.java | 177 ++++++++++++ .../gen/BalProjectOpenApiDocGenerator.java | 30 ++ .../oas/gen/DocGeneratorManager.java | 42 +++ .../oas/gen/OpenApiContractResolver.java | 128 +++++++++ .../oas/gen/OpenApiDocConfig.java | 41 +++ .../oas/gen/OpenApiDocGenerator.java | 31 +++ .../gen/SingleFileOpenApiDocGenerator.java | 54 ++++ .../HttpPayloadParamIdentifier.java | 74 ++--- .../PayloadAnnotationModifierTask.java | 24 +- .../context/PayloadParamAvailability.java} | 4 +- .../context/PayloadParamContext.java} | 12 +- .../context/PayloadParamData.java} | 6 +- .../context/ResourcePayloadParamContext.java} | 6 +- .../context/ServicePayloadParamContext.java} | 12 +- .../src/main/java/module-info.java | 3 + gradle.properties | 2 + 42 files changed, 1430 insertions(+), 180 deletions(-) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/{HttpDiagnosticCodes.java => HttpDiagnostic.java} (96%) create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{ => payload}/HttpPayloadParamIdentifier.java (84%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{ => payload}/PayloadAnnotationModifierTask.java (92%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{context/ParamAvailability.java => payload/context/PayloadParamAvailability.java} (94%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{context/DocumentContext.java => payload/context/PayloadParamContext.java} (77%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{context/ParamData.java => payload/context/PayloadParamData.java} (88%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{context/ResourceContext.java => payload/context/ResourcePayloadParamContext.java} (85%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{context/ServiceContext.java => payload/context/ServicePayloadParamContext.java} (77%) diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle index b529b6b1c2..87c9b8cc33 100644 --- a/compiler-plugin/build.gradle +++ b/compiler-plugin/build.gradle @@ -28,9 +28,13 @@ dependencies { checkstyle project(':checkstyle') checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" + implementation group: 'io.swagger.core.v3', name: 'swagger-core', version: "${swaggerVersion}" + implementation group: 'io.swagger.core.v3', name: 'swagger-models', version: "${swaggerVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" + + implementation group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" } def excludePattern = '**/module-info.java' diff --git a/compiler-plugin/spotbugs-exclude.xml b/compiler-plugin/spotbugs-exclude.xml index 3c56c8cc23..5ec731368d 100644 --- a/compiler-plugin/spotbugs-exclude.xml +++ b/compiler-plugin/spotbugs-exclude.xml @@ -42,13 +42,43 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java index bb8d59bafa..291ade6422 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java @@ -98,25 +98,25 @@ public final class HttpCompilerPluginUtil { private HttpCompilerPluginUtil() {} public static void updateDiagnostic(SyntaxNodeAnalysisContext ctx, Location location, - HttpDiagnosticCodes httpDiagnosticCodes) { + HttpDiagnostic httpDiagnosticCodes) { DiagnosticInfo diagnosticInfo = getDiagnosticInfo(httpDiagnosticCodes); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, location)); } public static void updateDiagnostic(SyntaxNodeAnalysisContext ctx, Location location, - HttpDiagnosticCodes httpDiagnosticCodes, Object... argName) { + HttpDiagnostic httpDiagnosticCodes, Object... argName) { DiagnosticInfo diagnosticInfo = getDiagnosticInfo(httpDiagnosticCodes, argName); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, location)); } public static void updateDiagnostic(SyntaxNodeAnalysisContext ctx, Location location, - HttpDiagnosticCodes httpDiagnosticCodes, + HttpDiagnostic httpDiagnosticCodes, List> diagnosticProperties, String argName) { DiagnosticInfo diagnosticInfo = getDiagnosticInfo(httpDiagnosticCodes, argName); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, location, diagnosticProperties)); } - public static DiagnosticInfo getDiagnosticInfo(HttpDiagnosticCodes diagnostic, Object... args) { + public static DiagnosticInfo getDiagnosticInfo(HttpDiagnostic diagnostic, Object... args) { return new DiagnosticInfo(diagnostic.getCode(), String.format(diagnostic.getMessage(), args), diagnostic.getSeverity()); } @@ -128,7 +128,7 @@ public static String getReturnTypeDescription(ReturnTypeDescriptorNode returnTyp public static void extractInterceptorReturnTypeAndValidate(SyntaxNodeAnalysisContext ctx, Map typeSymbols, FunctionDefinitionNode member, - HttpDiagnosticCodes httpDiagnosticCode) { + HttpDiagnostic httpDiagnosticCode) { Optional returnTypeDescriptorNode = member.functionSignature().returnTypeDesc(); if (returnTypeDescriptorNode.isEmpty()) { return; @@ -154,7 +154,7 @@ public static void extractInterceptorReturnTypeAndValidate(SyntaxNodeAnalysisCon public static void validateResourceReturnType(SyntaxNodeAnalysisContext ctx, Node node, Map typeSymbols, String returnTypeStringValue, - TypeSymbol returnTypeSymbol, HttpDiagnosticCodes diagnosticCode, + TypeSymbol returnTypeSymbol, HttpDiagnostic diagnosticCode, boolean isInterceptorType) { if (subtypeOf(typeSymbols, returnTypeSymbol, isInterceptorType ? INTERCEPTOR_RESOURCE_RETURN_TYPE : RESOURCE_RETURN_TYPE)) { @@ -200,16 +200,16 @@ public static TypeDescKind retrieveEffectiveTypeDesc(TypeSymbol descriptor) { } private static void reportInvalidReturnType(SyntaxNodeAnalysisContext ctx, Node node, - String returnType, HttpDiagnosticCodes diagnosticCode) { + String returnType, HttpDiagnostic diagnosticCode) { HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), diagnosticCode, returnType); } private static void reportReturnTypeAnnotationsAreNotAllowed(SyntaxNodeAnalysisContext ctx, Node node) { - HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_142); + HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_142); } public static void reportMissingParameterError(SyntaxNodeAnalysisContext ctx, Location location, String method) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_143, method); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_143, method); } public static String getNodeString(Node node, boolean isCaseSensitive) { 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/HttpDiagnostic.java similarity index 96% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnosticCodes.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnostic.java index 93aa3db518..b35b5018f6 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/HttpDiagnostic.java @@ -24,11 +24,12 @@ import static io.ballerina.stdlib.http.compiler.Constants.ALLOWED_RETURN_UNION; import static io.ballerina.tools.diagnostics.DiagnosticSeverity.ERROR; import static io.ballerina.tools.diagnostics.DiagnosticSeverity.INTERNAL; +import static io.ballerina.tools.diagnostics.DiagnosticSeverity.WARNING; /** * {@code DiagnosticCodes} is used to hold diagnostic codes. */ -public enum HttpDiagnosticCodes { +public enum HttpDiagnostic { HTTP_101("HTTP_101", "remote methods are not allowed in http:Service", ERROR), HTTP_102("HTTP_102", "invalid resource method return type: expected '" + ALLOWED_RETURN_UNION + "', but found '%s'", ERROR), @@ -124,6 +125,9 @@ public enum HttpDiagnosticCodes { 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_WARNING_101("HTTP_WARNING_101", "generated open-api definition is empty", WARNING), + HTTP_WARNING_102("HTTP_WARNING_102", "The openapi definition is overridden by the `embed: true` option", WARNING), + HTTP_HINT_101("HTTP_HINT_101", "Payload annotation can be added", INTERNAL), HTTP_HINT_102("HTTP_HINT_102", "Header annotation can be added", INTERNAL), HTTP_HINT_103("HTTP_HINT_103", "Response content-type can be added", INTERNAL), @@ -134,7 +138,7 @@ public enum HttpDiagnosticCodes { private final String message; private final DiagnosticSeverity severity; - HttpDiagnosticCodes(String code, String message, DiagnosticSeverity severity) { + HttpDiagnostic(String code, String message, DiagnosticSeverity severity) { this.code = code; this.message = message; this.severity = severity; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java index f7e9a62be8..950c5973a0 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorResourceValidator.java @@ -48,7 +48,7 @@ public static void validateResource(SyntaxNodeAnalysisContext ctx, FunctionDefin HttpResourceValidator.extractInputParamTypeAndValidate(ctx, functionNode, isRequestErrorInterceptor(type), typeSymbols); HttpCompilerPluginUtil.extractInterceptorReturnTypeAndValidate(ctx, typeSymbols, member, - HttpDiagnosticCodes.HTTP_126); + HttpDiagnostic.HTTP_126); } private static boolean isRequestErrorInterceptor(String type) { @@ -79,16 +79,16 @@ private static void checkResourceAnnotation(SyntaxNodeAnalysisContext ctx, } private static void reportResourceAnnotationNotAllowed(SyntaxNodeAnalysisContext ctx, AnnotationNode node) { - updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_125, + updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_125, node.annotReference().toString()); } private static void reportInvalidResourcePath(SyntaxNodeAnalysisContext ctx, Node node) { - updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_127, node.toString()); + updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_127, node.toString()); } private static void reportInvalidResourceMethod(SyntaxNodeAnalysisContext ctx, IdentifierToken identifierToken) { - updateDiagnostic(ctx, identifierToken.location(), HttpDiagnosticCodes.HTTP_128, + updateDiagnostic(ctx, identifierToken.location(), HttpDiagnostic.HTTP_128, identifierToken.toString().strip()); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorServiceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorServiceValidator.java index f58f856757..eb2290741b 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorServiceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpInterceptorServiceValidator.java @@ -218,7 +218,7 @@ private static void validateRemoteMethod(SyntaxNodeAnalysisContext ctx, Function String type, Map typeSymbols) { validateInputParamType(ctx, member, type, typeSymbols); HttpCompilerPluginUtil.extractInterceptorReturnTypeAndValidate(ctx, typeSymbols, member, - HttpDiagnosticCodes.HTTP_141); + HttpDiagnostic.HTTP_141); } private static void validateInputParamType(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode member, @@ -251,19 +251,19 @@ private static void validateInputParamType(SyntaxNodeAnalysisContext ctx, Functi TypeSymbol typeSymbol = param.typeDescriptor(); if (subtypeOf(typeSymbols, typeSymbol, CALLER_OBJ_NAME)) { callerPresent = isObjectPresent(ctx, paramLocation, callerPresent, paramName, - HttpDiagnosticCodes.HTTP_115); + HttpDiagnostic.HTTP_115); } else if (subtypeOf(typeSymbols, typeSymbol, REQUEST_OBJ_NAME)) { requestPresent = isObjectPresent(ctx, paramLocation, requestPresent, paramName, - HttpDiagnosticCodes.HTTP_116); + HttpDiagnostic.HTTP_116); } else if (subtypeOf(typeSymbols, typeSymbol, RESPONSE_OBJ_NAME)) { responsePresent = isObjectPresent(ctx, paramLocation, responsePresent, paramName, - HttpDiagnosticCodes.HTTP_139); + HttpDiagnostic.HTTP_139); } else if (subtypeOf(typeSymbols, typeSymbol, REQUEST_CONTEXT_OBJ_NAME)) { requestCtxPresent = isObjectPresent(ctx, paramLocation, requestCtxPresent, paramName, - HttpDiagnosticCodes.HTTP_121); + HttpDiagnostic.HTTP_121); } else if (isResponseErrorInterceptor(type) && kind == TypeDescKind.ERROR) { errorPresent = isObjectPresent(ctx, paramLocation, errorPresent, paramName, - HttpDiagnosticCodes.HTTP_122); + HttpDiagnostic.HTTP_122); } else { reportInvalidParameterType(ctx, paramLocation, paramType, isResponseErrorInterceptor(type)); } @@ -274,7 +274,7 @@ private static void validateInputParamType(SyntaxNodeAnalysisContext ctx, Functi } private static boolean isObjectPresent(SyntaxNodeAnalysisContext ctx, Location location, - boolean objectPresent, String paramName, HttpDiagnosticCodes code) { + boolean objectPresent, String paramName, HttpDiagnostic code) { if (objectPresent) { HttpCompilerPluginUtil.updateDiagnostic(ctx, location, code, paramName); } @@ -285,44 +285,44 @@ private static void reportInvalidParameterType(SyntaxNodeAnalysisContext ctx, Lo String typeName, boolean isResponseErrorInterceptor) { String functionName = isResponseErrorInterceptor ? Constants.INTERCEPT_RESPONSE_ERROR : Constants.INTERCEPT_RESPONSE; - HttpCompilerPluginUtil.updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_140, typeName, functionName); + HttpCompilerPluginUtil.updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_140, typeName, functionName); } private static void reportMultipleReferencesFound(SyntaxNodeAnalysisContext ctx, TypeReferenceNode node) { - HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_123, + HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_123, node.typeName().toString()); } private static void reportMultipleResourceFunctionsFound(SyntaxNodeAnalysisContext ctx, FunctionDefinitionNode node) { - DiagnosticInfo diagnosticInfo = new DiagnosticInfo(HttpDiagnosticCodes.HTTP_124.getCode(), - HttpDiagnosticCodes.HTTP_124.getMessage(), HttpDiagnosticCodes.HTTP_124.getSeverity()); + DiagnosticInfo diagnosticInfo = new DiagnosticInfo(HttpDiagnostic.HTTP_124.getCode(), + HttpDiagnostic.HTTP_124.getMessage(), HttpDiagnostic.HTTP_124.getSeverity()); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, node.location())); } private static void reportResourceFunctionNotFound(SyntaxNodeAnalysisContext ctx, String type) { - HttpCompilerPluginUtil.updateDiagnostic(ctx, ctx.node().location(), HttpDiagnosticCodes.HTTP_132, type); + HttpCompilerPluginUtil.updateDiagnostic(ctx, ctx.node().location(), HttpDiagnostic.HTTP_132, type); } private static void reportRemoteFunctionNotFound(SyntaxNodeAnalysisContext ctx, String type) { String requiredFunctionName = isResponseErrorInterceptor(type) ? Constants.INTERCEPT_RESPONSE_ERROR : Constants.INTERCEPT_RESPONSE; - DiagnosticInfo diagnosticInfo = HttpCompilerPluginUtil.getDiagnosticInfo(HttpDiagnosticCodes.HTTP_135, + DiagnosticInfo diagnosticInfo = HttpCompilerPluginUtil.getDiagnosticInfo(HttpDiagnostic.HTTP_135, type, requiredFunctionName); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, ctx.node().location())); } private static void reportResourceFunctionNotAllowed(SyntaxNodeAnalysisContext ctx, Node node, String type) { - HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_136, type); + HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_136, type); } private static void reportRemoteFunctionNotAllowed(SyntaxNodeAnalysisContext ctx, Node node, String type) { - HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_137, type); + HttpCompilerPluginUtil.updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_137, type); } private static void reportInvalidRemoteFunction(SyntaxNodeAnalysisContext ctx, Node node, String functionName, String interceptorType, String requiredFunctionName) { - DiagnosticInfo diagnosticInfo = HttpCompilerPluginUtil.getDiagnosticInfo(HttpDiagnosticCodes.HTTP_138, + DiagnosticInfo diagnosticInfo = HttpCompilerPluginUtil.getDiagnosticInfo(HttpDiagnostic.HTTP_138, functionName, interceptorType, requiredFunctionName); ctx.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, node.location())); } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java index 67c2e45b07..9cb7d7e4ec 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java @@ -56,8 +56,8 @@ import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ParamAvailability; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ParamData; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamAvailability; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamData; import io.ballerina.tools.diagnostics.Location; import org.wso2.ballerinalang.compiler.diagnostic.properties.BSymbolicProperty; @@ -123,8 +123,8 @@ import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.retrieveEffectiveTypeDesc; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.subtypeOf; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.updateDiagnostic; -import static io.ballerina.stdlib.http.compiler.codemodifier.HttpPayloadParamIdentifier.validateAnnotatedParams; -import static io.ballerina.stdlib.http.compiler.codemodifier.HttpPayloadParamIdentifier.validateNonAnnotatedParams; +import static io.ballerina.stdlib.http.compiler.codemodifier.payload.HttpPayloadParamIdentifier.validateAnnotatedParams; +import static io.ballerina.stdlib.http.compiler.codemodifier.payload.HttpPayloadParamIdentifier.validateNonAnnotatedParams; /** * Validates a ballerina http resource. @@ -342,22 +342,22 @@ public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ct if (kind == TypeDescKind.ERROR) { errorPresent = isObjectPresent(ctx, paramLocation, errorPresent, paramName, - HttpDiagnosticCodes.HTTP_122); + HttpDiagnostic.HTTP_122); } else if (subtypeOf(typeSymbols, typeSymbol, OBJECT)) { if (kind == TypeDescKind.INTERSECTION) { reportInvalidIntersectionObjectType(ctx, paramLocation, paramName, typeName); } else if (subtypeOf(typeSymbols, typeSymbol, CALLER_OBJ_NAME)) { callerPresent = isObjectPresent(ctx, paramLocation, callerPresent, paramName, - HttpDiagnosticCodes.HTTP_115); + HttpDiagnostic.HTTP_115); } else if (subtypeOf(typeSymbols, typeSymbol, REQUEST_OBJ_NAME)) { requestPresent = isObjectPresent(ctx, paramLocation, requestPresent, paramName, - HttpDiagnosticCodes.HTTP_116); + HttpDiagnostic.HTTP_116); } else if (subtypeOf(typeSymbols, typeSymbol, REQUEST_CONTEXT_OBJ_NAME)) { requestCtxPresent = isObjectPresent(ctx, paramLocation, requestCtxPresent, paramName, - HttpDiagnosticCodes.HTTP_121); + HttpDiagnostic.HTTP_121); } else if (subtypeOf(typeSymbols, typeSymbol, HEADER_OBJ_NAME)) { headersPresent = isObjectPresent(ctx, paramLocation, headersPresent, paramName, - HttpDiagnosticCodes.HTTP_117); + HttpDiagnostic.HTTP_117); } else { reportInvalidParameterType(ctx, paramLocation, paramType); } @@ -431,7 +431,7 @@ public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ct annotated = true; if (subtypeOf(typeSymbols, typeDescriptor, CALLER_OBJ_NAME)) { if (callerPresent) { - updateDiagnostic(ctx, paramLocation, HttpDiagnosticCodes.HTTP_115, paramName); + updateDiagnostic(ctx, paramLocation, HttpDiagnostic.HTTP_115, paramName); } else { callerPresent = true; Optional functionDefNode = member.getFunctionDefinitionNode(); @@ -502,30 +502,30 @@ private static void validatePathParam(SyntaxNodeAnalysisContext ctx, Map mockCodeModifier(SyntaxNodeAnalysisContext ctx, Map typeSymbols, Optional> parametersOptional) { - List nonAnnotatedParams = new ArrayList<>(); - List annotatedParams = new ArrayList<>(); + List nonAnnotatedParams = new ArrayList<>(); + List annotatedParams = new ArrayList<>(); List analyzedParams = new ArrayList<>(); - ParamAvailability paramAvailability = new ParamAvailability(); + PayloadParamAvailability paramAvailability = new PayloadParamAvailability(); int index = 0; for (ParameterSymbol param : parametersOptional.get()) { List annotations = param.annotations().stream() .filter(annotationSymbol -> annotationSymbol.typeDescriptor().isPresent()) .collect(Collectors.toList()); if (annotations.isEmpty()) { - nonAnnotatedParams.add(new ParamData(param, index++)); + nonAnnotatedParams.add(new PayloadParamData(param, index++)); } else { - annotatedParams.add(new ParamData(param, index++)); + annotatedParams.add(new PayloadParamData(param, index++)); } } - for (ParamData annotatedParam : annotatedParams) { + for (PayloadParamData annotatedParam : annotatedParams) { validateAnnotatedParams(annotatedParam.getParameterSymbol(), paramAvailability); if (paramAvailability.isAnnotatedPayloadParam()) { return analyzedParams; } } - for (ParamData nonAnnotatedParam : nonAnnotatedParams) { + for (PayloadParamData nonAnnotatedParam : nonAnnotatedParams) { ParameterSymbol parameterSymbol = nonAnnotatedParam.getParameterSymbol(); if (validateNonAnnotatedParams(ctx, parameterSymbol.typeDescriptor(), paramAvailability, @@ -709,23 +709,23 @@ private static void validateRecordFieldsOfHeaderParam(SyntaxNodeAnalysisContext private static void enableAddPayloadParamCodeAction(SyntaxNodeAnalysisContext ctx, Location location, String methodName) { if (!methodName.equals(GET) && !methodName.equals(HEAD) && !methodName.equals(OPTIONS)) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_HINT_101); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_HINT_101); } } private static void enableAddHeaderParamCodeAction(SyntaxNodeAnalysisContext ctx, Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_HINT_102); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_HINT_102); } private static void validatePayloadAnnotationUsage(SyntaxNodeAnalysisContext ctx, Location location, String methodName) { if (methodName.equals(GET) || methodName.equals(HEAD) || methodName.equals(OPTIONS)) { - reportInvalidUsageOfPayloadAnnotation(ctx, location, methodName, HttpDiagnosticCodes.HTTP_129); + reportInvalidUsageOfPayloadAnnotation(ctx, location, methodName, HttpDiagnostic.HTTP_129); } } private static boolean isObjectPresent(SyntaxNodeAnalysisContext ctx, Location location, - boolean objectPresent, String paramName, HttpDiagnosticCodes code) { + boolean objectPresent, String paramName, HttpDiagnostic code) { if (objectPresent) { updateDiagnostic(ctx, location, code, paramName); } @@ -796,7 +796,7 @@ private static void extractReturnTypeAndValidate(SyntaxNodeAnalysisContext ctx, return; } HttpCompilerPluginUtil.validateResourceReturnType(ctx, returnTypeNode, typeSymbols, returnTypeStringValue, - returnTypeSymbol.get(), HttpDiagnosticCodes.HTTP_102, false); + returnTypeSymbol.get(), HttpDiagnostic.HTTP_102, false); validateAnnotationsAndEnableCodeActions(ctx, returnTypeNode, returnTypeSymbol.get(), returnTypeStringValue, returnTypeDescriptorNode.get()); } @@ -829,7 +829,7 @@ private static void validateAnnotationsAndEnableCodeActions(SyntaxNodeAnalysisCo } else { if (payloadAnnotationPresent) { reportInvalidUsageOfPayloadAnnotation(ctx, returnTypeNode.location(), returnTypeString, - HttpDiagnosticCodes.HTTP_131); + HttpDiagnostic.HTTP_131); } if (cacheAnnotationPresent) { reportInvalidUsageOfCacheAnnotation(ctx, returnTypeNode.location(), returnTypeString); @@ -838,11 +838,11 @@ private static void validateAnnotationsAndEnableCodeActions(SyntaxNodeAnalysisCo } private static void enableConfigureReturnMediaTypeCodeAction(SyntaxNodeAnalysisContext ctx, Node node) { - updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_HINT_103); + updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_HINT_103); } private static void enableResponseCacheConfigCodeAction(SyntaxNodeAnalysisContext ctx, Node node) { - updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_HINT_104); + updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_HINT_104); } private static boolean checkForSupportedReturnTypes(TypeSymbol returnTypeSymbol) { @@ -902,7 +902,7 @@ public static void validateHttpCallerUsage(SyntaxNodeAnalysisContext ctx, Functi if (isValidReturnTypeWithCaller(typeSymbol)) { return; } - updateDiagnostic(ctx, returnTypeLocation, HttpDiagnosticCodes.HTTP_118, returnTypeDescription); + updateDiagnostic(ctx, returnTypeLocation, HttpDiagnostic.HTTP_118, returnTypeDescription); } public static boolean isHttpCaller(ParameterSymbol param) { @@ -936,96 +936,96 @@ private static boolean isValidReturnTypeWithCaller(TypeSymbol returnTypeDescript private static void reportInvalidParameterAnnotation(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_104, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_104, paramName); } private static void reportInvalidParameter(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_105, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_105, paramName); } private static void reportInvalidParameterType(SyntaxNodeAnalysisContext ctx, Location location, String typeName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_106, typeName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_106, typeName); } public static void reportInvalidPayloadParameterType(SyntaxNodeAnalysisContext ctx, Location location, String typeName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_107, typeName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_107, typeName); } private static void reportInvalidMultipleAnnotation(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_108, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_108, paramName); } private static void reportInvalidHeaderParameterType(SyntaxNodeAnalysisContext ctx, Location location, String paramName, Symbol parameterSymbol) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_109, List.of(new BSymbolicProperty(parameterSymbol)) + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_109, List.of(new BSymbolicProperty(parameterSymbol)) , paramName); } private static void reportInvalidUnionHeaderType(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_110, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_110, paramName); } private static void reportInvalidCallerParameterType(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_111, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_111, paramName); } private static void reportInvalidQueryParameterType(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_112, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_112, paramName); } private static void reportInvalidPathParameterType(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_145, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_145, paramName); } private static void reportInvalidUnionQueryType(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_113, paramName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_113, paramName); } private static void reportInCompatibleCallerInfoType(SyntaxNodeAnalysisContext ctx, PositionalArgumentNode node, String paramName) { - updateDiagnostic(ctx, node.location(), HttpDiagnosticCodes.HTTP_114, paramName); + updateDiagnostic(ctx, node.location(), HttpDiagnostic.HTTP_114, paramName); } private static void reportInvalidUsageOfPayloadAnnotation(SyntaxNodeAnalysisContext ctx, Location location, - String name, HttpDiagnosticCodes code) { + String name, HttpDiagnostic code) { updateDiagnostic(ctx, location, code, name); } private static void reportInvalidUsageOfCacheAnnotation(SyntaxNodeAnalysisContext ctx, Location location, String returnType) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_130, returnType); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_130, returnType); } private static void reportInvalidIntersectionType(SyntaxNodeAnalysisContext ctx, Location location, String typeName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_133, typeName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_133, typeName); } private static void reportInvalidIntersectionObjectType(SyntaxNodeAnalysisContext ctx, Location location, String paramName, String typeName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_134, paramName, typeName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_134, paramName, typeName); } private static void reportInvalidHeaderRecordRestFieldType(SyntaxNodeAnalysisContext ctx, Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_144); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_144); } private static void reportInvalidResourceName(SyntaxNodeAnalysisContext ctx, Location location, String resourceName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_146, resourceName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_146, resourceName); } private static void reportInvalidLinkRelation(SyntaxNodeAnalysisContext ctx, Location location, String relation) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_147, relation); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_147, relation); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java index e47e0ea935..203292cbd5 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceContractResourceValidator.java @@ -193,15 +193,15 @@ public static String constructResourcePathNameFromSegList(PathSegmentList pathSe private static void reportResourceFunctionNotAllowed(SyntaxNodeAnalysisContext ctx, String serviceContractType, Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_158, serviceContractType); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_158, serviceContractType); } private static void reportResourceConfigAnnotationNotAllowed(SyntaxNodeAnalysisContext ctx, Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_159); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_159); } private static void reportAnnotationNotAllowed(SyntaxNodeAnalysisContext ctx, Location location, String annotationName) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_160, annotationName); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_160, annotationName); } } 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 aa70234b98..72cf3c89e5 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 @@ -74,9 +74,9 @@ import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getCtxTypes; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpModule; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.updateDiagnostic; -import static io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes.HTTP_101; -import static io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes.HTTP_119; -import static io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes.HTTP_120; +import static io.ballerina.stdlib.http.compiler.HttpDiagnostic.HTTP_101; +import static io.ballerina.stdlib.http.compiler.HttpDiagnostic.HTTP_119; +import static io.ballerina.stdlib.http.compiler.HttpDiagnostic.HTTP_120; /** * Validates a Ballerina Http Service. @@ -446,26 +446,26 @@ private static void reportErrorMediaTypeSuffix(SyntaxNodeAnalysisContext ctx, St } private static void reportResourceNameDoesNotExist(SyntaxNodeAnalysisContext ctx, LinkedToResource resource) { - updateDiagnostic(ctx, resource.getNode().location(), HttpDiagnosticCodes.HTTP_148, resource.getName()); + updateDiagnostic(ctx, resource.getNode().location(), HttpDiagnostic.HTTP_148, resource.getName()); } private static void reportUnresolvedLinkedResource(SyntaxNodeAnalysisContext ctx, LinkedToResource resource) { - updateDiagnostic(ctx, resource.getNode().location(), HttpDiagnosticCodes.HTTP_149); + updateDiagnostic(ctx, resource.getNode().location(), HttpDiagnostic.HTTP_149); } private static void reportUnresolvedLinkedResourceWithMethod(SyntaxNodeAnalysisContext ctx, LinkedToResource resource) { - updateDiagnostic(ctx, resource.getNode().location(), HttpDiagnosticCodes.HTTP_150, resource.getMethod(), + updateDiagnostic(ctx, resource.getNode().location(), HttpDiagnostic.HTTP_150, resource.getMethod(), resource.getName()); } private static void reportInvalidServiceConfigAnnotationUsage(SyntaxNodeAnalysisContext ctx, Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_153); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_153); } private static void reportInvalidServiceContractType(SyntaxNodeAnalysisContext ctx, String expectedServiceType, String actualServiceType, Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_156, expectedServiceType, actualServiceType); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_156, expectedServiceType, actualServiceType); } private static void reportBasePathNotAllowed(SyntaxNodeAnalysisContext ctx, NodeList nodes) { @@ -474,19 +474,19 @@ private static void reportBasePathNotAllowed(SyntaxNodeAnalysisContext ctx, Node 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_154); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_154); } private static void reportBasePathFieldNotAllowed(SyntaxNodeAnalysisContext ctx, Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_155); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_155); } private static void reportServiceTypeNotAllowedFound(SyntaxNodeAnalysisContext ctx, NodeLocation location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_157); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_157); } private static void enableImplementServiceContractCodeAction(SyntaxNodeAnalysisContext ctx, String serviceType, NodeLocation location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_HINT_105, serviceType); + updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_HINT_105, serviceType); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddHeaderParameterCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddHeaderParameterCodeAction.java index a6362919be..37effb71ca 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddHeaderParameterCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddHeaderParameterCodeAction.java @@ -18,7 +18,7 @@ package io.ballerina.stdlib.http.compiler.codeaction; -import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; /** * CodeAction to add the annotated header parameter. @@ -27,7 +27,7 @@ public class AddHeaderParameterCodeAction extends AddResourceParameterCodeAction @Override protected String diagnosticCode() { - return HttpDiagnosticCodes.HTTP_HINT_102.getCode(); + return HttpDiagnostic.HTTP_HINT_102.getCode(); } protected String paramKind() { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorRemoteMethodCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorRemoteMethodCodeAction.java index 888acec1b3..88d1a332a8 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorRemoteMethodCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorRemoteMethodCodeAction.java @@ -18,7 +18,7 @@ package io.ballerina.stdlib.http.compiler.codeaction; -import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; import static io.ballerina.stdlib.http.compiler.codeaction.Constants.LS; import static io.ballerina.stdlib.http.compiler.codeaction.Constants.REMOTE; @@ -29,7 +29,7 @@ public class AddInterceptorRemoteMethodCodeAction extends AddInterceptorMethodCodeAction { @Override protected String diagnosticCode() { - return HttpDiagnosticCodes.HTTP_135.getCode(); + return HttpDiagnostic.HTTP_135.getCode(); } @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorResourceMethodCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorResourceMethodCodeAction.java index 99c50e8a4d..c8fc25c5b3 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorResourceMethodCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddInterceptorResourceMethodCodeAction.java @@ -18,7 +18,7 @@ package io.ballerina.stdlib.http.compiler.codeaction; -import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; import static io.ballerina.stdlib.http.compiler.codeaction.Constants.LS; import static io.ballerina.stdlib.http.compiler.codeaction.Constants.RESOURCE; @@ -29,7 +29,7 @@ public class AddInterceptorResourceMethodCodeAction extends AddInterceptorMethodCodeAction { @Override protected String diagnosticCode() { - return HttpDiagnosticCodes.HTTP_132.getCode(); + return HttpDiagnostic.HTTP_132.getCode(); } @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddPayloadParameterCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddPayloadParameterCodeAction.java index 197eee2141..4853fcb2bd 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddPayloadParameterCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddPayloadParameterCodeAction.java @@ -18,7 +18,7 @@ package io.ballerina.stdlib.http.compiler.codeaction; -import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; /** * CodeAction to add the annotated payload parameter. @@ -27,7 +27,7 @@ public class AddPayloadParameterCodeAction extends AddResourceParameterCodeActio @Override protected String diagnosticCode() { - return HttpDiagnosticCodes.HTTP_HINT_101.getCode(); + return HttpDiagnostic.HTTP_HINT_101.getCode(); } protected String paramKind() { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java index b55c630657..7072e08a54 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseCacheConfigCodeAction.java @@ -26,7 +26,7 @@ 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.stdlib.http.compiler.HttpDiagnostic; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextDocumentChange; @@ -47,7 +47,7 @@ public class AddResponseCacheConfigCodeAction implements CodeAction { @Override public List supportedDiagnosticCodes() { - return List.of(HttpDiagnosticCodes.HTTP_HINT_104.getCode()); + return List.of(HttpDiagnostic.HTTP_HINT_104.getCode()); } @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java index abd878b306..9652c233f7 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddResponseContentTypeCodeAction.java @@ -26,7 +26,7 @@ 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.stdlib.http.compiler.HttpDiagnostic; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextDocumentChange; @@ -47,7 +47,7 @@ public class AddResponseContentTypeCodeAction implements CodeAction { @Override public List supportedDiagnosticCodes() { - return List.of(HttpDiagnosticCodes.HTTP_HINT_103.getCode()); + return List.of(HttpDiagnostic.HTTP_HINT_103.getCode()); } @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java index 47cbaa5872..20aeb134e2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeHeaderParamTypeCodeAction.java @@ -29,7 +29,7 @@ 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.stdlib.http.compiler.HttpDiagnostic; import io.ballerina.tools.diagnostics.DiagnosticProperty; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; @@ -53,7 +53,7 @@ public abstract class ChangeHeaderParamTypeCodeAction implements CodeAction { @Override public List supportedDiagnosticCodes() { - return List.of(HttpDiagnosticCodes.HTTP_109.getCode()); + return List.of(HttpDiagnostic.HTTP_109.getCode()); } @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java index 98ef874ed0..cc19b3e212 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeReturnTypeWithCallerCodeAction.java @@ -24,7 +24,7 @@ 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.stdlib.http.compiler.HttpDiagnostic; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextDocumentChange; @@ -47,7 +47,7 @@ public class ChangeReturnTypeWithCallerCodeAction implements CodeAction { @Override public List supportedDiagnosticCodes() { - return List.of(HttpDiagnosticCodes.HTTP_118.getCode()); + return List.of(HttpDiagnostic.HTTP_118.getCode()); } @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java index 4a9040f9fc..03527f6a05 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java @@ -37,7 +37,7 @@ 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.stdlib.http.compiler.HttpDiagnostic; import io.ballerina.tools.text.LineRange; import io.ballerina.tools.text.TextDocument; import io.ballerina.tools.text.TextDocumentChange; @@ -66,7 +66,7 @@ public class ImplementServiceContract implements CodeAction { @Override public List supportedDiagnosticCodes() { - return List.of(HttpDiagnosticCodes.HTTP_HINT_105.getCode()); + return List.of(HttpDiagnostic.HTTP_HINT_105.getCode()); } @Override diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java index c2ad4ea204..bc548e22e7 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java @@ -22,7 +22,10 @@ import io.ballerina.projects.DocumentId; import io.ballerina.projects.plugins.CodeModifier; import io.ballerina.projects.plugins.CodeModifierContext; -import io.ballerina.stdlib.http.compiler.codemodifier.context.DocumentContext; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.OpenApiInfoUpdaterTask; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.HttpPayloadParamIdentifier; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.PayloadAnnotationModifierTask; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamContext; import java.util.HashMap; import java.util.List; @@ -34,7 +37,7 @@ * @since 2201.5.0 */ public class HttpServiceModifier extends CodeModifier { - private final Map payloadParamContextMap; + private final Map payloadParamContextMap; public HttpServiceModifier() { this.payloadParamContextMap = new HashMap<>(); @@ -46,6 +49,8 @@ public void init(CodeModifierContext codeModifierContext) { new HttpPayloadParamIdentifier(this.payloadParamContextMap), List.of(SyntaxKind.SERVICE_DECLARATION, SyntaxKind.CLASS_DEFINITION, SyntaxKind.OBJECT_TYPE_DESC)); codeModifierContext.addSourceModifierTask(new PayloadAnnotationModifierTask(this.payloadParamContextMap)); + + codeModifierContext.addSourceModifierTask(new OpenApiInfoUpdaterTask()); codeModifierContext.addSourceModifierTask(new ServiceTypeModifierTask()); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java new file mode 100644 index 0000000000..395549c211 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas; + +/** + * {@code Constants} contains the common constants. + */ +public interface Constants { + // package details related constants + String PACKAGE_ORG = "ballerina"; + String PACKAGE_NAME = "openapi"; + + // open-api module related constants + String SERVICE_INFO_ANNOTATION_IDENTIFIER = "ServiceInfo"; + String CONTRACT = "contract"; + String EMBED = "embed"; + + // http module related constants + String HTTP_PACKAGE_NAME = "http"; + String SERVICE_CONFIG_ANNOTATION_IDENTIFIER = "ServiceConfig"; + String OPEN_API_DEFINITION_FIELD = "openApiDefinition"; + + String SLASH = "/"; +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java new file mode 100644 index 0000000000..85c2ecb543 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas; + +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.DiagnosticFactory; +import io.ballerina.tools.diagnostics.DiagnosticInfo; +import io.ballerina.tools.diagnostics.Location; + +/** + * {@code DocGenerationUtils} contains common utilities related to doc generation and resource packaging. + */ +public final class DocGenerationUtils { + public static Diagnostic getDiagnostics(HttpDiagnostic errorCode, + Location location, Object... args) { + DiagnosticInfo diagnosticInfo = new DiagnosticInfo( + errorCode.getCode(), errorCode.getMessage(), errorCode.getSeverity()); + return DiagnosticFactory.createDiagnostic(diagnosticInfo, location, args); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java new file mode 100644 index 0000000000..3eac491719 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.ModuleSymbol; +import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.api.symbols.TypeDescKind; +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.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.projects.Project; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.DocGeneratorManager; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.OpenApiDocConfig; + +import java.util.Optional; + +/** + * {@code HttpServiceAnalysisTask} analyses the HTTP service for which the OpenApi doc is generated. + */ +public class HttpServiceAnalysisTask { + private final DocGeneratorManager docGenerator; + + public HttpServiceAnalysisTask() { + this.docGenerator = new DocGeneratorManager(); + } + + public void perform(ServiceNodeAnalysisContext context) { + Project currentProject = context.currentPackage().project(); + ServiceDeclarationNode serviceNode = context.node(); + SemanticModel semanticModel = context.semanticModel(); + Optional serviceDeclarationOpt = semanticModel.symbol(serviceNode); + if (serviceDeclarationOpt.isPresent()) { + ServiceDeclarationSymbol serviceSymbol = (ServiceDeclarationSymbol) serviceDeclarationOpt.get(); + if (!isHttpService(serviceSymbol)) { + return; + } + SyntaxTree syntaxTree = context.syntaxTree(); + OpenApiDocConfig docConfig = new OpenApiDocConfig(context.currentPackage(), + semanticModel, syntaxTree, serviceNode, currentProject.kind()); + this.docGenerator.generate(docConfig, context, serviceNode.location()); + } + } + + private boolean isHttpService(ServiceDeclarationSymbol serviceSymbol) { + return serviceSymbol.listenerTypes().stream().anyMatch(this::isHttpListener); + } + + private boolean isHttpListener(TypeSymbol listenerType) { + if (listenerType.typeKind() == TypeDescKind.UNION) { + return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() + .filter(typeDescriptor -> typeDescriptor instanceof TypeReferenceTypeSymbol) + .map(typeReferenceTypeSymbol -> (TypeReferenceTypeSymbol) typeReferenceTypeSymbol) + .anyMatch(typeReferenceTypeSymbol -> + typeReferenceTypeSymbol.getModule().isPresent() + && isHttp(typeReferenceTypeSymbol.getModule().get() + )); + } + + if (listenerType.typeKind() == TypeDescKind.TYPE_REFERENCE) { + Optional moduleOpt = ((TypeReferenceTypeSymbol) listenerType).typeDescriptor().getModule(); + return moduleOpt.isPresent() && isHttp(moduleOpt.get()); + } + + if (listenerType.typeKind() == TypeDescKind.OBJECT) { + Optional moduleOpt = listenerType.getModule(); + return moduleOpt.isPresent() && isHttp(moduleOpt.get()); + } + + return false; + } + + private boolean isHttp(ModuleSymbol moduleSymbol) { + Optional moduleNameOpt = moduleSymbol.getName(); + return moduleNameOpt.isPresent() && Constants.HTTP_PACKAGE_NAME.equals(moduleNameOpt.get()) + && Constants.PACKAGE_ORG.equals(moduleSymbol.id().orgName()); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java new file mode 100644 index 0000000000..44762d3f3e --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.IdentifierToken; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingFieldNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; +import io.ballerina.compiler.syntax.tree.ModulePartNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeFactory; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.NodeParser; +import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; +import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.compiler.syntax.tree.Token; +import io.ballerina.projects.Document; +import io.ballerina.projects.DocumentId; +import io.ballerina.projects.Module; +import io.ballerina.projects.ModuleId; +import io.ballerina.projects.Package; +import io.ballerina.projects.plugins.ModifierTask; +import io.ballerina.projects.plugins.SourceModifierContext; +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; +import io.ballerina.tools.diagnostics.DiagnosticFactory; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import io.ballerina.tools.text.TextDocument; + +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Base64; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getDiagnosticInfo; +import static io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContextHandler.getContextHandler; + +/** + * {@code OpenApiInfoUpdaterTask} modifies the source by including generated open-api spec for http-service + * declarations. + */ +public class OpenApiInfoUpdaterTask implements ModifierTask { + @Override + public void modify(SourceModifierContext context) { + boolean erroneousCompilation = context.compilation().diagnosticResult() + .diagnostics().stream() + .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); + // if the compilation already contains any error, do not proceed + if (erroneousCompilation) { + return; + } + + // Mocking the code analysis task + mockServiceAnalyzerExecution(context); + + for (OpenApiDocContext openApiContext: getContextHandler().retrieveAvailableContexts()) { + ModuleId moduleId = openApiContext.getModuleId(); + Module currentModule = context.currentPackage().module(moduleId); + DocumentId documentId = openApiContext.getDocumentId(); + Document currentDoc = currentModule.document(documentId); + ModulePartNode rootNode = currentDoc.syntaxTree().rootNode(); + NodeList newMembers = updateMemberNodes( + rootNode.members(), openApiContext.getOpenApiDetails(), context); + ModulePartNode newModulePart = rootNode.modify(rootNode.imports(), newMembers, rootNode.eofToken()); + SyntaxTree updatedSyntaxTree = currentDoc.syntaxTree().modifyWith(newModulePart); + TextDocument textDocument = updatedSyntaxTree.textDocument(); + if (currentModule.documentIds().contains(documentId)) { + context.modifySourceFile(textDocument, documentId); + } else { + context.modifyTestSourceFile(textDocument, documentId); + } + } + } + + private static void mockServiceAnalyzerExecution(SourceModifierContext context) { + Package currentPackage = context.currentPackage(); + HttpServiceAnalysisTask serviceAnalysisTask = new HttpServiceAnalysisTask(); + for (ModuleId moduleId : currentPackage.moduleIds()) { + Module currentModule = currentPackage.module(moduleId); + SemanticModel semanticModel = context.compilation().getSemanticModel(moduleId); + for (DocumentId documentId : currentModule.documentIds()) { + Document currentDoc = currentModule.document(documentId); + SyntaxTree syntaxTree = currentDoc.syntaxTree(); + ModulePartNode rootNode = syntaxTree.rootNode(); + NodeList members = rootNode.members(); + for (ModuleMemberDeclarationNode member: members) { + if (member.kind() != SyntaxKind.SERVICE_DECLARATION) { + continue; + } + ServiceNodeAnalysisContext serviceNodeAnalysisContext = new ServiceNodeAnalysisContext( + currentPackage, moduleId, documentId, syntaxTree, semanticModel, + (ServiceDeclarationNode) member); + serviceAnalysisTask.perform(serviceNodeAnalysisContext); + serviceNodeAnalysisContext.diagnostics().forEach(context::reportDiagnostic); + } + } + } + } + + private NodeList updateMemberNodes(NodeList oldMembers, + List openApi, + SourceModifierContext context) { + List updatedMembers = new LinkedList<>(); + for (ModuleMemberDeclarationNode memberNode : oldMembers) { + if (memberNode.kind() != SyntaxKind.SERVICE_DECLARATION) { + updatedMembers.add(memberNode); + continue; + } + ServiceDeclarationNode serviceNode = (ServiceDeclarationNode) memberNode; + Optional openApiDefOpt = openApi.stream() + .filter(service -> service.getServiceId() == serviceNode.hashCode()) + .findFirst(); + if (openApiDefOpt.isEmpty()) { + updatedMembers.add(memberNode); + continue; + } + OpenApiDocContext.OpenApiDefinition openApiDef = openApiDefOpt.get(); + if (!openApiDef.isAutoEmbedToService()) { + updatedMembers.add(memberNode); + continue; + } + MetadataNode metadataNode = getMetadataNode(serviceNode); + MetadataNode.MetadataNodeModifier modifier = metadataNode.modify(); + NodeList updatedAnnotations = updateAnnotations( + metadataNode.annotations(), openApiDef.getDefinition(), context); + modifier.withAnnotations(updatedAnnotations); + MetadataNode updatedMetadataNode = modifier.apply(); + ServiceDeclarationNode.ServiceDeclarationNodeModifier serviceDecModifier = serviceNode.modify(); + serviceDecModifier.withMetadata(updatedMetadataNode); + ServiceDeclarationNode updatedServiceDecNode = serviceDecModifier.apply(); + updatedMembers.add(updatedServiceDecNode); + } + return AbstractNodeFactory.createNodeList(updatedMembers); + } + + private MetadataNode getMetadataNode(ServiceDeclarationNode serviceNode) { + return serviceNode.metadata().orElseGet(() -> { + NodeList annotations = NodeFactory.createNodeList(); + return NodeFactory.createMetadataNode(null, annotations); + }); + } + + private NodeList updateAnnotations(NodeList currentAnnotations, + String openApiDefinition, SourceModifierContext context) { + NodeList updatedAnnotations = NodeFactory.createNodeList(); + boolean openApiAnnotationUpdated = false; + for (AnnotationNode annotation: currentAnnotations) { + if (isHttpServiceConfigAnnotation(annotation)) { + openApiAnnotationUpdated = true; + SeparatedNodeList updatedFields = getUpdatedFields(annotation, openApiDefinition, + context); + MappingConstructorExpressionNode annotationValue = + NodeFactory.createMappingConstructorExpressionNode( + NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN), updatedFields, + NodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); + annotation = annotation.modify().withAnnotValue(annotationValue).apply(); + } + updatedAnnotations = updatedAnnotations.add(annotation); + } + if (!openApiAnnotationUpdated) { + AnnotationNode openApiAnnotation = getHttpServiceConfigAnnotation(openApiDefinition); + updatedAnnotations = updatedAnnotations.add(openApiAnnotation); + } + return updatedAnnotations; + } + + private SeparatedNodeList getUpdatedFields(AnnotationNode annotation, String servicePath, + SourceModifierContext context) { + Optional annotationValueOpt = annotation.annotValue(); + if (annotationValueOpt.isEmpty()) { + return NodeFactory.createSeparatedNodeList(createOpenApiDefinitionField(servicePath)); + } + List fields = new ArrayList<>(); + MappingConstructorExpressionNode annotationValue = annotationValueOpt.get(); + SeparatedNodeList existingFields = annotationValue.fields(); + Token separator = NodeFactory.createToken(SyntaxKind.COMMA_TOKEN); + MappingFieldNode openApiDefNode = null; + for (MappingFieldNode field : existingFields) { + if (field instanceof SpecificFieldNode) { + String fieldName = ((SpecificFieldNode) field).fieldName().toString(); + if (Constants.OPEN_API_DEFINITION_FIELD.equals(fieldName.trim())) { + openApiDefNode = field; + continue; + } + } + fields.add(field); + fields.add(separator); + } + fields.add(createOpenApiDefinitionField(servicePath)); + if (Objects.nonNull(openApiDefNode)) { + context.reportDiagnostic(DiagnosticFactory.createDiagnostic( + getDiagnosticInfo(HttpDiagnostic.HTTP_WARNING_102), openApiDefNode.location())); + } + return NodeFactory.createSeparatedNodeList(fields); + } + + private AnnotationNode getHttpServiceConfigAnnotation(String openApiDefinition) { + String configIdentifierString = Constants.HTTP_PACKAGE_NAME + SyntaxKind.COLON_TOKEN.stringValue() + + Constants.SERVICE_CONFIG_ANNOTATION_IDENTIFIER; + IdentifierToken identifierToken = NodeFactory.createIdentifierToken(configIdentifierString); + Token atToken = NodeFactory.createToken(SyntaxKind.AT_TOKEN); + SimpleNameReferenceNode nameReferenceNode = NodeFactory.createSimpleNameReferenceNode(identifierToken); + MappingConstructorExpressionNode annotValue = getAnnotationExpression(openApiDefinition); + return NodeFactory.createAnnotationNode(atToken, nameReferenceNode, annotValue); + } + + private MappingConstructorExpressionNode getAnnotationExpression(String openApiDefinition) { + Token openBraceToken = NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN); + Token closeBraceToken = NodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN); + SpecificFieldNode specificFieldNode = createOpenApiDefinitionField(openApiDefinition); + SeparatedNodeList separatedNodeList = NodeFactory.createSeparatedNodeList(specificFieldNode); + return NodeFactory.createMappingConstructorExpressionNode(openBraceToken, separatedNodeList, closeBraceToken); + } + + private static SpecificFieldNode createOpenApiDefinitionField(String openApiDefinition) { + IdentifierToken fieldName = AbstractNodeFactory.createIdentifierToken(Constants.OPEN_API_DEFINITION_FIELD); + Token colonToken = AbstractNodeFactory.createToken(SyntaxKind.COLON_TOKEN); + String encodedValue = Base64.getEncoder().encodeToString(openApiDefinition.getBytes(Charset.defaultCharset())); + ExpressionNode expressionNode = NodeParser.parseExpression( + String.format("base64 `%s`.cloneReadOnly()", encodedValue)); + return NodeFactory.createSpecificFieldNode(null, fieldName, colonToken, expressionNode); + } + + private boolean isHttpServiceConfigAnnotation(AnnotationNode annotationNode) { + if (!(annotationNode.annotReference() instanceof QualifiedNameReferenceNode referenceNode)) { + return false; + } + if (!Constants.HTTP_PACKAGE_NAME.equals(referenceNode.modulePrefix().text())) { + return false; + } + return Constants.SERVICE_CONFIG_ANNOTATION_IDENTIFIER.equals(referenceNode.identifier().text()); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java new file mode 100644 index 0000000000..fb6b908107 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.context; + +import io.ballerina.projects.DocumentId; +import io.ballerina.projects.ModuleId; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * {@code OpenApiDocContext} contains details related to open-api doc generation. + */ +public class OpenApiDocContext { + private final ModuleId moduleId; + private final DocumentId documentId; + private final List definitions = new ArrayList<>(); + + OpenApiDocContext(ModuleId moduleId, DocumentId documentId) { + this.moduleId = moduleId; + this.documentId = documentId; + } + + public ModuleId getModuleId() { + return moduleId; + } + + public DocumentId getDocumentId() { + return documentId; + } + + public List getOpenApiDetails() { + return Collections.unmodifiableList(definitions); + } + + void updateOpenApiDetails(OpenApiDefinition definition) { + this.definitions.add(definition); + } + + /** + * {@code OpenApiDefinition} contains details related to generated open-api definition. + */ + public static class OpenApiDefinition { + private final int serviceId; + private final String definition; + private final boolean embed; + + public OpenApiDefinition(int serviceId, String definition, boolean embed) { + this.serviceId = serviceId; + this.definition = definition; + this.embed = embed; + } + + public int getServiceId() { + return serviceId; + } + + public String getDefinition() { + return definition; + } + + public boolean isAutoEmbedToService() { + return embed; + } + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java new file mode 100644 index 0000000000..52dca294f4 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.context; + +import io.ballerina.projects.DocumentId; +import io.ballerina.projects.ModuleId; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * {@code OpenApiDocContextHandler} will manage the shared context among compiler plugin tasks. + */ +public final class OpenApiDocContextHandler { + private static OpenApiDocContextHandler instance; + + private final List contexts; + + private OpenApiDocContextHandler(List contexts) { + this.contexts = contexts; + } + + public static OpenApiDocContextHandler getContextHandler() { + synchronized (OpenApiDocContextHandler.class) { + if (Objects.isNull(instance)) { + instance = new OpenApiDocContextHandler(new ArrayList<>()); + } + } + return instance; + } + + private void addContext(OpenApiDocContext context) { + synchronized (this.contexts) { + this.contexts.add(context); + } + } + + /** + * Update the shared context for open-api doc generation. + * @param moduleId of the current module + * @param documentId of the current file + * @param definition to be added to the context + */ + public void updateContext(ModuleId moduleId, DocumentId documentId, + OpenApiDocContext.OpenApiDefinition definition) { + Optional contextOpt = retrieveContext(moduleId, documentId); + if (contextOpt.isPresent()) { + OpenApiDocContext context = contextOpt.get(); + synchronized (context) { + context.updateOpenApiDetails(definition); + } + return; + } + OpenApiDocContext context = new OpenApiDocContext(moduleId, documentId); + context.updateOpenApiDetails(definition); + addContext(context); + } + + private Optional retrieveContext(ModuleId moduleId, DocumentId documentId) { + return this.contexts.stream() + .filter(ctx -> equals(ctx, moduleId, documentId)) + .findFirst(); + } + + public List retrieveAvailableContexts() { + return Collections.unmodifiableList(contexts); + } + + private boolean equals(OpenApiDocContext context, ModuleId moduleId, DocumentId documentId) { + int hashCodeForCurrentContext = Objects.hash(context.getModuleId(), context.getDocumentId()); + return hashCodeForCurrentContext == Objects.hash(moduleId, documentId); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java new file mode 100644 index 0000000000..f1c337e842 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.context; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.projects.DocumentId; +import io.ballerina.projects.ModuleId; +import io.ballerina.projects.Package; +import io.ballerina.tools.diagnostics.Diagnostic; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@code ServiceNodeAnalysisContext} will store service node analysis context. + */ +public class ServiceNodeAnalysisContext { + + private final ServiceDeclarationNode node; + private final ModuleId moduleId; + private final DocumentId documentId; + private final SyntaxTree syntaxTree; + private final SemanticModel semanticModel; + private final Package currentPackage; + private final List diagnostics; + + public ServiceNodeAnalysisContext(Package currentPackage, ModuleId moduleId, DocumentId documentId, + SyntaxTree syntaxTree, SemanticModel semanticModel, + ServiceDeclarationNode node) { + this.moduleId = moduleId; + this.documentId = documentId; + this.syntaxTree = syntaxTree; + this.semanticModel = semanticModel; + this.currentPackage = currentPackage; + this.diagnostics = new ArrayList<>(); + this.node = node; + } + + public ServiceDeclarationNode node() { + return node; + } + + public ModuleId moduleId() { + return moduleId; + } + + public DocumentId documentId() { + return documentId; + } + + public SyntaxTree syntaxTree() { + return syntaxTree; + } + + public SemanticModel semanticModel() { + return semanticModel; + } + + public Package currentPackage() { + return currentPackage; + } + + public void reportDiagnostic(Diagnostic diagnosticCode) { + diagnostics.add(diagnosticCode); + } + + public List diagnostics() { + return diagnostics; + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java new file mode 100644 index 0000000000..f8cacdc040 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.gen; + +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.NodeLocation; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper; +import io.ballerina.openapi.service.mapper.model.OASGenerationMetaInfo; +import io.ballerina.openapi.service.mapper.model.OASResult; +import io.ballerina.projects.Package; +import io.ballerina.projects.Project; +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Diagnostic; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.OpenAPI; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.normalizeTitle; +import static io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContextHandler.getContextHandler; +import static io.ballerina.stdlib.http.compiler.codemodifier.oas.DocGenerationUtils.getDiagnostics; + +/** + * {@code AbstractOpenApiDocGenerator} contains the basic utilities required for OpenAPI doc generation. + */ +public abstract class AbstractOpenApiDocGenerator implements OpenApiDocGenerator { + private static final String FILE_NAME_FORMAT = "%d.json"; + + private final OpenApiContractResolver contractResolver; + + public AbstractOpenApiDocGenerator() { + this.contractResolver = new OpenApiContractResolver(); + } + + @Override + public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, NodeLocation location) { + try { + int serviceId = config.getServiceId(); + Package currentPackage = config.currentPackage(); + Path srcRoot = currentPackage.project().sourceRoot(); + + // find the project root path + Path projectRoot = retrieveProjectRoot(srcRoot); + + ServiceDeclarationNode serviceNode = config.serviceNode(); + Optional serviceInfoAnnotationOpt = getServiceInfoAnnotation(serviceNode); + if (serviceInfoAnnotationOpt.isPresent()) { + AnnotationNode serviceInfoAnnotation = serviceInfoAnnotationOpt.get(); + + boolean embed = retrieveValueForAnnotationFields( + serviceInfoAnnotation, Constants.EMBED) + .map(Boolean::parseBoolean) + .orElse(false); + + // use the available open-api doc and update the context + OpenApiContractResolver.ResolverResponse resolverResponse = this.contractResolver + .resolve(serviceInfoAnnotation, projectRoot); + if (resolverResponse.isContractAvailable()) { + // could not find the open-api contract file, hence will not proceed + if (resolverResponse.getContractPath().isEmpty()) { + return; + } + String openApiDefinition = Files.readString(resolverResponse.getContractPath().get()); + updateOpenApiContext(context, serviceId, openApiDefinition, embed); + } else { + // generate open-api doc and update the context if the `contract` configuration is not available + generateOpenApiDoc(currentPackage.project(), config, context, location, embed); + } + } + } catch (IOException | RuntimeException e) { + // currently, we do not have open-api doc generation logic for following scenarios: + // 1. default resources and for scenarios + // 2. returning http-response from a resource + // hence logs are disabled for now + } + } + + private void updateOpenApiContext(ServiceNodeAnalysisContext context, int serviceId, String openApiDefinition, + boolean embed) { + OpenApiDocContext.OpenApiDefinition openApiDef = new OpenApiDocContext.OpenApiDefinition(serviceId, + openApiDefinition, embed); + getContextHandler().updateContext(context.moduleId(), context.documentId(), openApiDef); + } + + private void updateCompilerContext(ServiceNodeAnalysisContext context, NodeLocation location, + HttpDiagnostic errorCode) { + Diagnostic diagnostic = getDiagnostics(errorCode, location); + context.reportDiagnostic(diagnostic); + } + + private Optional getServiceInfoAnnotation(ServiceDeclarationNode serviceNode) { + Optional metadata = serviceNode.metadata(); + if (metadata.isEmpty()) { + return Optional.empty(); + } + MetadataNode metaData = metadata.get(); + NodeList annotations = metaData.annotations(); + String serviceInfoAnnotation = String.format("%s:%s", + Constants.PACKAGE_NAME, Constants.SERVICE_INFO_ANNOTATION_IDENTIFIER); + return annotations.stream() + .filter(ann -> serviceInfoAnnotation.equals(ann.annotReference().toString().trim())) + .findFirst(); + } + + private Optional retrieveValueForAnnotationFields(AnnotationNode serviceInfoAnnotation, String fieldName) { + return serviceInfoAnnotation + .annotValue() + .map(MappingConstructorExpressionNode::fields) + .flatMap(fields -> + fields.stream() + .filter(fld -> fld instanceof SpecificFieldNode) + .map(fld -> (SpecificFieldNode) fld) + .filter(fld -> fieldName.equals(fld.fieldName().toString().trim())) + .findFirst() + ).flatMap(SpecificFieldNode::valueExpr) + .map(en -> en.toString().trim()); + } + + private void generateOpenApiDoc(Project project, OpenApiDocConfig config, ServiceNodeAnalysisContext context, + NodeLocation location, boolean embed) { + if (!embed) { + return; + } + int serviceId = config.getServiceId(); + String targetFile = String.format(FILE_NAME_FORMAT, serviceId); + OASGenerationMetaInfo.OASGenerationMetaInfoBuilder builder = new + OASGenerationMetaInfo.OASGenerationMetaInfoBuilder(); + builder.setServiceDeclarationNode(config.serviceNode()) + .setSemanticModel(config.semanticModel()) + .setOpenApiFileName(targetFile) + .setBallerinaFilePath(null) + .setProject(project); + OASResult oasResult = ServiceToOpenAPIMapper.generateOAS(builder.build()); + Optional openApiOpt = oasResult.getOpenAPI(); + if (!oasResult.getDiagnostics().isEmpty() || openApiOpt.isEmpty()) { + HttpDiagnostic errorCode = HttpDiagnostic.HTTP_WARNING_101; + updateCompilerContext(context, location, errorCode); + return; + } + OpenAPI openApi = openApiOpt.get(); + if (openApi.getInfo().getTitle() == null || openApi.getInfo().getTitle().equals(Constants.SLASH)) { + openApi.getInfo().setTitle(normalizeTitle(targetFile)); + } + String openApiDefinition = Json.pretty(openApi); + updateOpenApiContext(context, serviceId, openApiDefinition, true); + } + + protected Path retrieveProjectRoot(Path projectRoot) { + return projectRoot; + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java new file mode 100644 index 0000000000..a31c71cdf1 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.gen; + +import io.ballerina.projects.ProjectKind; + +/** + * {@code BalProjectOpenApiDocGenerator} generates open-api related docs for HTTP service defined in ballerina projects. + */ +public class BalProjectOpenApiDocGenerator extends AbstractOpenApiDocGenerator { + @Override + public boolean isSupported(ProjectKind projectType) { + return ProjectKind.BUILD_PROJECT.equals(projectType); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java new file mode 100644 index 0000000000..2934d17ef4 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.gen; + +import io.ballerina.compiler.syntax.tree.NodeLocation; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; + +import java.util.List; + +/** + * {@code DocGeneratorManager} manages OpenAPI doc generation for HTTP services depending on whether the current project + * is a ballerina-project or a single ballerina file. + */ +public final class DocGeneratorManager { + private final List docGenerators; + + public DocGeneratorManager() { + this.docGenerators = List.of(new SingleFileOpenApiDocGenerator(), new BalProjectOpenApiDocGenerator()); + } + + public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, NodeLocation location) { + docGenerators.stream() + .filter(dg -> dg.isSupported(config.projectType())) + .findFirst() + .ifPresent(dg -> dg.generate(config, context, location)); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java new file mode 100644 index 0000000000..164e4909ea --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.gen; + +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingFieldNode; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; +import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +/** + * {@code OpenApiContractResolver} resolves the provided OpenAPI doc via `openapi:ServiceInfo` annotation. + */ +public final class OpenApiContractResolver { + public ResolverResponse resolve(AnnotationNode serviceInfoAnnotation, Path projectRoot) { + Optional mappingConstructorExpressionNode = + serviceInfoAnnotation.annotValue(); + if (mappingConstructorExpressionNode.isEmpty()) { + // if details not available do not proceed + return new ResolverResponse(false); + } + + MappingConstructorExpressionNode exprNode = mappingConstructorExpressionNode.get(); + SeparatedNodeList fieldsOpt = exprNode.fields(); + if (fieldsOpt.isEmpty()) { + // if details not available do not proceed + return new ResolverResponse(false); + } + + Optional> annotationFieldsOpt = serviceInfoAnnotation + .annotValue().map(MappingConstructorExpressionNode::fields); + if (annotationFieldsOpt.isEmpty()) { + // annotation fields are not available, hence will not proceed + return new ResolverResponse(false); + } + + SeparatedNodeList annotationFields = annotationFieldsOpt.get(); + Optional contractFieldOpt = annotationFields.stream() + .filter(fld -> fld instanceof SpecificFieldNode) + .map(fld -> (SpecificFieldNode) fld) + .filter(fld -> Constants.CONTRACT.equals(fld.fieldName().toString().trim())) + .findFirst(); + if (contractFieldOpt.isEmpty()) { + // could not find the `contract` field in the service-info annotation, hence will not proceed + return new ResolverResponse(false); + } + + SpecificFieldNode openApiContract = contractFieldOpt.get(); + Optional openApiContractValueOpt = openApiContract.valueExpr(); + if (openApiContractValueOpt.isEmpty()) { + // could not find the value for `contract` field in the service-info annotation, + // hence will not proceed + return new ResolverResponse(true); + } + + ExpressionNode openApiContractValue = openApiContractValueOpt.get(); + String openApiContractPath = openApiContractValue.toString() + .replaceAll("\"", "").trim(); + if (openApiContractPath.isBlank()) { + // `contract` value is empty, hence will not proceed + return new ResolverResponse(true); + } + + Path pathToOpenApiContract = getPathToOpenApiContract(openApiContractPath, projectRoot); + if (!Files.exists(pathToOpenApiContract)) { + // could not find open-api contract file, hence will not proceed + return new ResolverResponse(true); + } + + return new ResolverResponse(pathToOpenApiContract); + } + + private Path getPathToOpenApiContract(String openApiPath, Path projectRoot) { + Path openApiDocPath = Paths.get(openApiPath); + if (openApiDocPath.isAbsolute()) { + return openApiDocPath; + } else { + return projectRoot.resolve(openApiDocPath); + } + } + + /** + * {@code ResolverResponse} contains the response for OpenAPI doc retrieval via `openapi:ServiceInfo` annotation. + */ + public static class ResolverResponse { + private final boolean contractAvailable; + private Path contractPath; + + ResolverResponse(boolean contractAvailable) { + this.contractAvailable = contractAvailable; + } + + ResolverResponse(Path contractPath) { + this.contractAvailable = true; + this.contractPath = contractPath; + } + + public boolean isContractAvailable() { + return contractAvailable; + } + + public Optional getContractPath() { + return Optional.ofNullable(this.contractPath); + } + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java new file mode 100644 index 0000000000..de7dd2cfd8 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.gen; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.projects.Package; +import io.ballerina.projects.ProjectKind; + +/** + * {@code OpenApiDocConfig} contains the configurations related to generate OpenAPI doc. + * + * @param currentPackage current package + * @param semanticModel semantic model + * @param syntaxTree syntax tree + * @param serviceNode service node + * @param projectType project type + */ +public record OpenApiDocConfig(Package currentPackage, SemanticModel semanticModel, SyntaxTree syntaxTree, + ServiceDeclarationNode serviceNode, ProjectKind projectType) { + + public int getServiceId() { + return serviceNode.hashCode(); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java new file mode 100644 index 0000000000..f9e3699565 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.gen; + +import io.ballerina.compiler.syntax.tree.NodeLocation; +import io.ballerina.projects.ProjectKind; +import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; + +/** + * {@code Generator} generates open-api related docs for HTTP service. + */ +public interface OpenApiDocGenerator { + void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, NodeLocation location); + + boolean isSupported(ProjectKind projectType); +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java new file mode 100644 index 0000000000..a771d3c7c8 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. + * + * 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.codemodifier.oas.gen; + +import io.ballerina.projects.ProjectKind; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; + +/** + * {@code SingleFileOpenApiDocGenerator} generates open-api related docs for HTTP service defined in single ballerina + * file. + */ +public class SingleFileOpenApiDocGenerator extends AbstractOpenApiDocGenerator { + @Override + public boolean isSupported(ProjectKind projectType) { + return ProjectKind.SINGLE_FILE_PROJECT.equals(projectType); + } + + @Override + protected Path retrieveProjectRoot(Path projectRoot) { + // For single ballerina file, project root will be the absolute path for that particular ballerina file + // hence project root should be updated to the directory which contains the ballerina file + Path parentDirectory = retrieveParentDirectory(projectRoot); + if (Objects.nonNull(parentDirectory) && Files.exists(parentDirectory)) { + return parentDirectory; + } + return projectRoot.isAbsolute() ? projectRoot : projectRoot.toAbsolutePath(); + } + + private Path retrieveParentDirectory(Path projectRoot) { + if (projectRoot.isAbsolute()) { + return projectRoot.getParent(); + } else { + return projectRoot.toAbsolutePath().getParent(); + } + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java similarity index 84% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java index 331267a6f2..21ed4d453d 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpPayloadParamIdentifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier; +package io.ballerina.stdlib.http.compiler.codemodifier.payload; import io.ballerina.compiler.api.symbols.AnnotationSymbol; import io.ballerina.compiler.api.symbols.ParameterSymbol; @@ -38,17 +38,17 @@ import io.ballerina.projects.DocumentId; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.stdlib.http.compiler.Constants; -import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; +import io.ballerina.stdlib.http.compiler.HttpDiagnostic; import io.ballerina.stdlib.http.compiler.HttpResourceValidator; import io.ballerina.stdlib.http.compiler.HttpServiceValidator; import io.ballerina.stdlib.http.compiler.ResourceFunction; import io.ballerina.stdlib.http.compiler.ResourceFunctionDeclaration; import io.ballerina.stdlib.http.compiler.ResourceFunctionDefinition; -import io.ballerina.stdlib.http.compiler.codemodifier.context.DocumentContext; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ParamAvailability; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ParamData; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ResourceContext; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ServiceContext; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamAvailability; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamContext; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamData; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.ResourcePayloadParamContext; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.ServicePayloadParamContext; import java.util.ArrayList; import java.util.List; @@ -83,9 +83,9 @@ * @since 2201.5.0 */ public class HttpPayloadParamIdentifier extends HttpServiceValidator { - private final Map documentContextMap; + private final Map documentContextMap; - public HttpPayloadParamIdentifier(Map documentContextMap) { + public HttpPayloadParamIdentifier(Map documentContextMap) { this.documentContextMap = documentContextMap; } @@ -113,20 +113,20 @@ private void validateServiceDeclaration(SyntaxNodeAnalysisContext syntaxNodeAnal return; } NodeList members = serviceDeclarationNode.members(); - ServiceContext serviceContext = new ServiceContext(serviceDeclarationNode.hashCode()); + ServicePayloadParamContext serviceContext = new ServicePayloadParamContext(serviceDeclarationNode.hashCode()); validateResources(syntaxNodeAnalysisContext, typeSymbols, members, serviceContext); } private void validateServiceObjDefinition(SyntaxNodeAnalysisContext context, Map typeSymbols) { ObjectTypeDescriptorNode serviceObjType = (ObjectTypeDescriptorNode) context.node(); NodeList members = serviceObjType.members(); - ServiceContext serviceContext = new ServiceContext(serviceObjType.hashCode()); + ServicePayloadParamContext serviceContext = new ServicePayloadParamContext(serviceObjType.hashCode()); validateResources(context, typeSymbols, members, serviceContext); } private void validateResources(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, Map typeSymbols, NodeList members, - ServiceContext serviceContext) { + ServicePayloadParamContext serviceContext) { for (Node member : members) { if (member.kind() == SyntaxKind.RESOURCE_ACCESSOR_DEFINITION) { validateResource(syntaxNodeAnalysisContext, @@ -151,7 +151,7 @@ private void validateClassDefinition(SyntaxNodeAnalysisContext syntaxNodeAnalysi return; } NodeList members = classDefinitionNode.members(); - ServiceContext serviceContext = new ServiceContext(classDefinitionNode.hashCode()); + ServicePayloadParamContext serviceContext = new ServicePayloadParamContext(classDefinitionNode.hashCode()); boolean proceed = false; for (Node member : members) { if (member.kind() == SyntaxKind.TYPE_REFERENCE) { @@ -172,13 +172,15 @@ private void validateClassDefinition(SyntaxNodeAnalysisContext syntaxNodeAnalysi } } - void validateResource(SyntaxNodeAnalysisContext ctx, ResourceFunction member, ServiceContext serviceContext, + void validateResource(SyntaxNodeAnalysisContext ctx, ResourceFunction member, + ServicePayloadParamContext serviceContext, Map typeSymbols) { extractInputParamTypeAndValidate(ctx, member, serviceContext, typeSymbols); } void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, ResourceFunction member, - ServiceContext serviceContext, Map typeSymbols) { + ServicePayloadParamContext serviceContext, + Map typeSymbols) { Optional resourceMethodSymbolOptional = member.getSymbol(ctx.semanticModel()); if (resourceMethodSymbolOptional.isEmpty()) { @@ -202,9 +204,9 @@ void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, ResourceFun return; // No modification is done for non param resources functions } - List nonAnnotatedParams = new ArrayList<>(); - List annotatedParams = new ArrayList<>(); - ParamAvailability paramAvailability = new ParamAvailability(); + List nonAnnotatedParams = new ArrayList<>(); + List annotatedParams = new ArrayList<>(); + PayloadParamAvailability paramAvailability = new PayloadParamAvailability(); // Disable error diagnostic in the code modifier since this validation is also done in the code analyzer paramAvailability.setErrorDiagnostic(false); int index = 0; @@ -213,29 +215,29 @@ void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, ResourceFun .filter(annotationSymbol -> annotationSymbol.typeDescriptor().isPresent()) .collect(Collectors.toList()); if (annotations.isEmpty()) { - nonAnnotatedParams.add(new ParamData(param, index++)); + nonAnnotatedParams.add(new PayloadParamData(param, index++)); } else { - annotatedParams.add(new ParamData(param, index++)); + annotatedParams.add(new PayloadParamData(param, index++)); } } - for (ParamData annotatedParam : annotatedParams) { + for (PayloadParamData annotatedParam : annotatedParams) { validateAnnotatedParams(annotatedParam.getParameterSymbol(), paramAvailability); if (paramAvailability.isAnnotatedPayloadParam()) { return; } } - for (ParamData nonAnnotatedParam : nonAnnotatedParams) { + for (PayloadParamData nonAnnotatedParam : nonAnnotatedParams) { ParameterSymbol parameterSymbol = nonAnnotatedParam.getParameterSymbol(); if (validateNonAnnotatedParams(ctx, parameterSymbol.typeDescriptor(), paramAvailability, parameterSymbol, typeSymbols)) { - ResourceContext resourceContext = - new ResourceContext(parameterSymbol, nonAnnotatedParam.getIndex()); - DocumentContext documentContext = documentContextMap.get(ctx.documentId()); + ResourcePayloadParamContext resourceContext = + new ResourcePayloadParamContext(parameterSymbol, nonAnnotatedParam.getIndex()); + PayloadParamContext documentContext = documentContextMap.get(ctx.documentId()); if (documentContext == null) { - documentContext = new DocumentContext(ctx); + documentContext = new PayloadParamContext(ctx); documentContextMap.put(ctx.documentId(), documentContext); } serviceContext.setResourceContext(resourceId, resourceContext); @@ -248,7 +250,8 @@ void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ctx, ResourceFun } } - public static void validateAnnotatedParams(ParameterSymbol parameterSymbol, ParamAvailability paramAvailability) { + public static void validateAnnotatedParams(ParameterSymbol parameterSymbol, + PayloadParamAvailability paramAvailability) { List annotations = parameterSymbol.annotations().stream() .filter(annotationSymbol -> annotationSymbol.typeDescriptor().isPresent()) .collect(Collectors.toList()); @@ -269,7 +272,7 @@ public static void validateAnnotatedParams(ParameterSymbol parameterSymbol, Para } public static boolean validateNonAnnotatedParams(SyntaxNodeAnalysisContext analysisContext, - TypeSymbol typeSymbol, ParamAvailability paramAvailability, + TypeSymbol typeSymbol, PayloadParamAvailability paramAvailability, ParameterSymbol parameterSymbol, Map typeSymbols) { typeSymbol = getEffectiveType(typeSymbol); @@ -298,7 +301,8 @@ public static boolean validateNonAnnotatedParams(SyntaxNodeAnalysisContext analy } private static boolean isUnionStructuredType(SyntaxNodeAnalysisContext ctx, UnionTypeSymbol unionTypeSymbol, - ParameterSymbol parameterSymbol, ParamAvailability paramAvailability, + ParameterSymbol parameterSymbol, + PayloadParamAvailability paramAvailability, Map typeSymbols) { List typeDescriptors = unionTypeSymbol.memberTypeDescriptors(); boolean foundNonStructuredType = false; @@ -347,7 +351,7 @@ private static boolean isStructuredType(Map typeSymbols, Typ } private static boolean checkErrorsAndReturn(SyntaxNodeAnalysisContext analysisContext, - ParamAvailability availability, ParameterSymbol pSymbol) { + PayloadParamAvailability availability, ParameterSymbol pSymbol) { if (availability.isDefaultPayloadParam() && isDistinctVariable(availability, pSymbol)) { reportAmbiguousPayloadParam(analysisContext, pSymbol, availability); availability.setErrorOccurred(true); @@ -357,28 +361,28 @@ private static boolean checkErrorsAndReturn(SyntaxNodeAnalysisContext analysisCo return true; } - private static boolean isDistinctVariable(ParamAvailability availability, ParameterSymbol pSymbol) { + private static boolean isDistinctVariable(PayloadParamAvailability availability, ParameterSymbol pSymbol) { return !pSymbol.getName().get().equals(availability.getPayloadParamSymbol().getName().get()); } private static void reportAmbiguousPayloadParam(SyntaxNodeAnalysisContext analysisContext, ParameterSymbol parameterSymbol, - ParamAvailability paramAvailability) { + PayloadParamAvailability paramAvailability) { if (paramAvailability.isEnableErrorDiagnostic()) { - updateDiagnostic(analysisContext, parameterSymbol.getLocation().get(), HttpDiagnosticCodes.HTTP_151, + updateDiagnostic(analysisContext, parameterSymbol.getLocation().get(), HttpDiagnostic.HTTP_151, paramAvailability.getPayloadParamSymbol().getName().get(), parameterSymbol.getName().get()); } } private static void reportInvalidUnionPayloadParam(SyntaxNodeAnalysisContext analysisContext, ParameterSymbol parameterSymbol, - ParamAvailability paramAvailability) { + PayloadParamAvailability paramAvailability) { if (paramAvailability.isErrorOccurred()) { return; } if (!paramAvailability.isDefaultPayloadParam()) { if (paramAvailability.isEnableErrorDiagnostic()) { - updateDiagnostic(analysisContext, parameterSymbol.getLocation().get(), HttpDiagnosticCodes.HTTP_152, + updateDiagnostic(analysisContext, parameterSymbol.getLocation().get(), HttpDiagnostic.HTTP_152, parameterSymbol.getName().get()); } paramAvailability.setErrorOccurred(true); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java similarity index 92% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java index fd87e86e07..24d4befcc7 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/PayloadAnnotationModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier; +package io.ballerina.stdlib.http.compiler.codemodifier.payload; import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; import io.ballerina.compiler.syntax.tree.AnnotationNode; @@ -50,9 +50,9 @@ import io.ballerina.stdlib.http.compiler.ResourceFunction; import io.ballerina.stdlib.http.compiler.ResourceFunctionDeclaration; import io.ballerina.stdlib.http.compiler.ResourceFunctionDefinition; -import io.ballerina.stdlib.http.compiler.codemodifier.context.DocumentContext; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ResourceContext; -import io.ballerina.stdlib.http.compiler.codemodifier.context.ServiceContext; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamContext; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.ResourcePayloadParamContext; +import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.ServicePayloadParamContext; import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.text.TextDocument; @@ -71,9 +71,9 @@ */ public class PayloadAnnotationModifierTask implements ModifierTask { - private final Map documentContextMap; + private final Map documentContextMap; - public PayloadAnnotationModifierTask(Map documentContextMap) { + public PayloadAnnotationModifierTask(Map documentContextMap) { this.documentContextMap = documentContextMap; } @@ -86,15 +86,15 @@ public void modify(SourceModifierContext modifierContext) { return; } - for (Map.Entry entry : documentContextMap.entrySet()) { + for (Map.Entry entry : documentContextMap.entrySet()) { DocumentId documentId = entry.getKey(); - DocumentContext documentContext = entry.getValue(); + PayloadParamContext documentContext = entry.getValue(); modifyPayloadParam(modifierContext, documentId, documentContext); } } private void modifyPayloadParam(SourceModifierContext modifierContext, DocumentId documentId, - DocumentContext documentContext) { + PayloadParamContext documentContext) { ModuleId moduleId = documentId.moduleId(); Module currentModule = modifierContext.currentPackage().module(moduleId); Document currentDoc = currentModule.document(documentId); @@ -111,7 +111,7 @@ private void modifyPayloadParam(SourceModifierContext modifierContext, DocumentI } private NodeList updateMemberNodes(NodeList oldMembers, - DocumentContext documentContext) { + PayloadParamContext documentContext) { List updatedMembers = new ArrayList<>(); for (ModuleMemberDeclarationNode memberNode : oldMembers) { @@ -142,7 +142,7 @@ private NodeList updateMemberNodes(NodeList resourceMembers = new ArrayList<>(); for (Node member : members) { ResourceFunction resourceFunctionNode; @@ -161,7 +161,7 @@ private NodeList updateMemberNodes(NodeList parameterNodes = functionSignatureNode.parameters(); List newParameterNodes = new ArrayList<>(); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ParamAvailability.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamAvailability.java similarity index 94% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ParamAvailability.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamAvailability.java index 88746fef2c..6e9678c539 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ParamAvailability.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamAvailability.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier.context; +package io.ballerina.stdlib.http.compiler.codemodifier.payload.context; import io.ballerina.compiler.api.symbols.ParameterSymbol; @@ -25,7 +25,7 @@ * * @since 2201.5.0 */ -public class ParamAvailability { +public class PayloadParamAvailability { private boolean annotatedPayloadParam = false; private boolean errorOccurred = false; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/DocumentContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamContext.java similarity index 77% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/DocumentContext.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamContext.java index fa6cfc1838..2a2445c4f6 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/DocumentContext.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamContext.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier.context; +package io.ballerina.stdlib.http.compiler.codemodifier.payload.context; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; @@ -28,11 +28,11 @@ * * @since 2201.5.0 */ -public class DocumentContext { +public class PayloadParamContext { private final SyntaxNodeAnalysisContext context; - private final Map serviceContextMap; + private final Map serviceContextMap; - public DocumentContext(SyntaxNodeAnalysisContext context) { + public PayloadParamContext(SyntaxNodeAnalysisContext context) { this.context = context; this.serviceContextMap = new HashMap<>(); } @@ -41,7 +41,7 @@ public SyntaxNodeAnalysisContext getContext() { return context; } - public void setServiceContext(ServiceContext serviceContext) { + public void setServiceContext(ServicePayloadParamContext serviceContext) { serviceContextMap.put(serviceContext.getServiceId(), serviceContext); } @@ -49,7 +49,7 @@ public boolean containsService(int serviceId) { return serviceContextMap.containsKey(serviceId); } - public ServiceContext getServiceContext(int serviceId) { + public ServicePayloadParamContext getServiceContext(int serviceId) { return serviceContextMap.get(serviceId); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ParamData.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamData.java similarity index 88% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ParamData.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamData.java index 4e92ea9b41..46b0f13206 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ParamData.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/PayloadParamData.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier.context; +package io.ballerina.stdlib.http.compiler.codemodifier.payload.context; import io.ballerina.compiler.api.symbols.ParameterSymbol; @@ -25,12 +25,12 @@ * * @since 2201.5.0 */ -public class ParamData { +public class PayloadParamData { private ParameterSymbol parameterSymbol; private int index; - public ParamData(ParameterSymbol parameterSymbol, int index) { + public PayloadParamData(ParameterSymbol parameterSymbol, int index) { this.setParameterSymbol(parameterSymbol); this.setIndex(index); } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ResourceContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/ResourcePayloadParamContext.java similarity index 85% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ResourceContext.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/ResourcePayloadParamContext.java index f488430303..5eb74cac45 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ResourceContext.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/ResourcePayloadParamContext.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier.context; +package io.ballerina.stdlib.http.compiler.codemodifier.payload.context; import io.ballerina.compiler.api.symbols.ParameterSymbol; @@ -25,11 +25,11 @@ * * @since 2201.5.0 */ -public class ResourceContext { +public class ResourcePayloadParamContext { private final int index; private final ParameterSymbol parameterSymbol; - public ResourceContext(ParameterSymbol parameterSymbol, int index) { + public ResourcePayloadParamContext(ParameterSymbol parameterSymbol, int index) { this.parameterSymbol = parameterSymbol; this.index = index; } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ServiceContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/ServicePayloadParamContext.java similarity index 77% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ServiceContext.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/ServicePayloadParamContext.java index 2ac018d3da..eacce6003f 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/context/ServiceContext.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/context/ServicePayloadParamContext.java @@ -16,7 +16,7 @@ * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier.context; +package io.ballerina.stdlib.http.compiler.codemodifier.payload.context; import java.util.HashMap; import java.util.Map; @@ -26,16 +26,16 @@ * * @since 2201.5.0 */ -public class ServiceContext { +public class ServicePayloadParamContext { private final int serviceId; - private final Map resourceContextMap; + private final Map resourceContextMap; - public ServiceContext(int serviceId) { + public ServicePayloadParamContext(int serviceId) { this.serviceId = serviceId; this.resourceContextMap = new HashMap<>(); } - public void setResourceContext(int resourceId, ResourceContext payloadParamInfo) { + public void setResourceContext(int resourceId, ResourcePayloadParamContext payloadParamInfo) { this.resourceContextMap.put(resourceId, payloadParamInfo); } @@ -51,7 +51,7 @@ public boolean containsResource(int resourceId) { return resourceContextMap.containsKey(resourceId); } - public ResourceContext getResourceContext(int resourceId) { + public ResourcePayloadParamContext getResourceContext(int resourceId) { return resourceContextMap.get(resourceId); } } diff --git a/compiler-plugin/src/main/java/module-info.java b/compiler-plugin/src/main/java/module-info.java index 8372628273..66d065826f 100644 --- a/compiler-plugin/src/main/java/module-info.java +++ b/compiler-plugin/src/main/java/module-info.java @@ -20,4 +20,7 @@ requires io.ballerina.lang; requires io.ballerina.tools.api; requires io.ballerina.parser; + requires io.swagger.v3.core; + requires io.swagger.v3.oas.models; + requires io.ballerina.openapi.service; } diff --git a/gradle.properties b/gradle.properties index e8ad947235..6e2e3620ce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,6 +23,8 @@ lz4Version=1.3.0 marshallingVersion=2.0.5.Final protobufVersion=3.20.3 jacocoVersion=0.8.10 +ballerinaToOpenApiVersion=2.0.2 +swaggerVersion=2.2.9 stdlibIoVersion=1.6.0 stdlibTimeVersion=2.4.0 From 27c9b4f399b40a332375cfefa5b87817ec212231 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 27 Jun 2024 10:58:21 +0530 Subject: [PATCH 18/47] Fix code modifier executions --- ballerina/http_annotation.bal | 12 ++ .../compiler/codemodifier/oas/Constants.java | 3 +- .../oas/HttpServiceAnalysisTask.java | 62 +--------- .../oas/OpenApiInfoUpdaterTask.java | 116 +++++++++--------- .../context/ServiceNodeAnalysisContext.java | 8 +- .../oas/gen/AbstractOpenApiDocGenerator.java | 26 ++-- .../oas/gen/DocGeneratorManager.java | 4 +- .../oas/gen/OpenApiDocConfig.java | 6 +- .../oas/gen/OpenApiDocGenerator.java | 4 +- gradle.properties | 2 +- 10 files changed, 107 insertions(+), 136 deletions(-) diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index 25c6118f72..a24729de03 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -156,3 +156,15 @@ 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; + +# Defines the information about the service contract. +# +# + openApiDefinition - The generated OpenAPI definition for the HTTP service. This is auto-generated at +# compile-time by default +public type ServiceContractInformation record {| + string openApiDefinition; +|}; + +# The annotation which is used to define the information about the service contract. This annotation is auto-generated +# at compile-time by default. Adding this annotation manually is not recommended. +public const annotation ServiceContractInformation ServiceContractInfo on type; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java index 395549c211..9c860b84d0 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java @@ -32,7 +32,8 @@ public interface Constants { // http module related constants String HTTP_PACKAGE_NAME = "http"; - String SERVICE_CONFIG_ANNOTATION_IDENTIFIER = "ServiceConfig"; + String SERVICE_CONFIG = "ServiceConfig"; + String SERVICE_CONTRACT_INFO = "ServiceContractInfo"; String OPEN_API_DEFINITION_FIELD = "openApiDefinition"; String SLASH = "/"; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java index 3eac491719..2cfe6776cf 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java @@ -18,22 +18,13 @@ package io.ballerina.stdlib.http.compiler.codemodifier.oas; import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.api.symbols.ModuleSymbol; -import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; -import io.ballerina.compiler.api.symbols.Symbol; -import io.ballerina.compiler.api.symbols.TypeDescKind; -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.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.openapi.service.mapper.model.ServiceNode; import io.ballerina.projects.Project; import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.DocGeneratorManager; import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.OpenApiDocConfig; -import java.util.Optional; - /** * {@code HttpServiceAnalysisTask} analyses the HTTP service for which the OpenApi doc is generated. */ @@ -46,52 +37,11 @@ public HttpServiceAnalysisTask() { public void perform(ServiceNodeAnalysisContext context) { Project currentProject = context.currentPackage().project(); - ServiceDeclarationNode serviceNode = context.node(); + ServiceNode serviceNode = context.node(); SemanticModel semanticModel = context.semanticModel(); - Optional serviceDeclarationOpt = semanticModel.symbol(serviceNode); - if (serviceDeclarationOpt.isPresent()) { - ServiceDeclarationSymbol serviceSymbol = (ServiceDeclarationSymbol) serviceDeclarationOpt.get(); - if (!isHttpService(serviceSymbol)) { - return; - } - SyntaxTree syntaxTree = context.syntaxTree(); - OpenApiDocConfig docConfig = new OpenApiDocConfig(context.currentPackage(), - semanticModel, syntaxTree, serviceNode, currentProject.kind()); - this.docGenerator.generate(docConfig, context, serviceNode.location()); - } - } - - private boolean isHttpService(ServiceDeclarationSymbol serviceSymbol) { - return serviceSymbol.listenerTypes().stream().anyMatch(this::isHttpListener); - } - - private boolean isHttpListener(TypeSymbol listenerType) { - if (listenerType.typeKind() == TypeDescKind.UNION) { - return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() - .filter(typeDescriptor -> typeDescriptor instanceof TypeReferenceTypeSymbol) - .map(typeReferenceTypeSymbol -> (TypeReferenceTypeSymbol) typeReferenceTypeSymbol) - .anyMatch(typeReferenceTypeSymbol -> - typeReferenceTypeSymbol.getModule().isPresent() - && isHttp(typeReferenceTypeSymbol.getModule().get() - )); - } - - if (listenerType.typeKind() == TypeDescKind.TYPE_REFERENCE) { - Optional moduleOpt = ((TypeReferenceTypeSymbol) listenerType).typeDescriptor().getModule(); - return moduleOpt.isPresent() && isHttp(moduleOpt.get()); - } - - if (listenerType.typeKind() == TypeDescKind.OBJECT) { - Optional moduleOpt = listenerType.getModule(); - return moduleOpt.isPresent() && isHttp(moduleOpt.get()); - } - - return false; - } - - private boolean isHttp(ModuleSymbol moduleSymbol) { - Optional moduleNameOpt = moduleSymbol.getName(); - return moduleNameOpt.isPresent() && Constants.HTTP_PACKAGE_NAME.equals(moduleNameOpt.get()) - && Constants.PACKAGE_ORG.equals(moduleSymbol.id().orgName()); + SyntaxTree syntaxTree = context.syntaxTree(); + OpenApiDocConfig docConfig = new OpenApiDocConfig(context.currentPackage(), + semanticModel, syntaxTree, serviceNode, currentProject.kind()); + this.docGenerator.generate(docConfig, context, serviceNode.location()); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java index 44762d3f3e..9ff96b3279 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java @@ -33,12 +33,12 @@ import io.ballerina.compiler.syntax.tree.NodeParser; import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; import io.ballerina.compiler.syntax.tree.SeparatedNodeList; -import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.SyntaxTree; import io.ballerina.compiler.syntax.tree.Token; +import io.ballerina.openapi.service.mapper.model.ServiceNode; import io.ballerina.projects.Document; import io.ballerina.projects.DocumentId; import io.ballerina.projects.Module; @@ -61,7 +61,10 @@ import java.util.Objects; import java.util.Optional; +import static io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper.getServiceNode; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getDiagnosticInfo; +import static io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants.SERVICE_CONFIG; +import static io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants.SERVICE_CONTRACT_INFO; import static io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContextHandler.getContextHandler; /** @@ -87,9 +90,10 @@ public void modify(SourceModifierContext context) { Module currentModule = context.currentPackage().module(moduleId); DocumentId documentId = openApiContext.getDocumentId(); Document currentDoc = currentModule.document(documentId); + SemanticModel semanticModel = context.compilation().getSemanticModel(moduleId); ModulePartNode rootNode = currentDoc.syntaxTree().rootNode(); NodeList newMembers = updateMemberNodes( - rootNode.members(), openApiContext.getOpenApiDetails(), context); + rootNode.members(), openApiContext.getOpenApiDetails(), context, semanticModel); ModulePartNode newModulePart = rootNode.modify(rootNode.imports(), newMembers, rootNode.eofToken()); SyntaxTree updatedSyntaxTree = currentDoc.syntaxTree().modifyWith(newModulePart); TextDocument textDocument = updatedSyntaxTree.textDocument(); @@ -113,12 +117,13 @@ private static void mockServiceAnalyzerExecution(SourceModifierContext context) ModulePartNode rootNode = syntaxTree.rootNode(); NodeList members = rootNode.members(); for (ModuleMemberDeclarationNode member: members) { - if (member.kind() != SyntaxKind.SERVICE_DECLARATION) { + Optional serviceNode = getServiceNode(member, semanticModel); + if (serviceNode.isEmpty()) { continue; } ServiceNodeAnalysisContext serviceNodeAnalysisContext = new ServiceNodeAnalysisContext( - currentPackage, moduleId, documentId, syntaxTree, semanticModel, - (ServiceDeclarationNode) member); + currentPackage, moduleId, documentId, syntaxTree, semanticModel, + serviceNode.get()); serviceAnalysisTask.perform(serviceNodeAnalysisContext); serviceNodeAnalysisContext.diagnostics().forEach(context::reportDiagnostic); } @@ -128,56 +133,54 @@ private static void mockServiceAnalyzerExecution(SourceModifierContext context) private NodeList updateMemberNodes(NodeList oldMembers, List openApi, - SourceModifierContext context) { + SourceModifierContext context, + SemanticModel semanticModel) { List updatedMembers = new LinkedList<>(); for (ModuleMemberDeclarationNode memberNode : oldMembers) { - if (memberNode.kind() != SyntaxKind.SERVICE_DECLARATION) { - updatedMembers.add(memberNode); - continue; - } - ServiceDeclarationNode serviceNode = (ServiceDeclarationNode) memberNode; - Optional openApiDefOpt = openApi.stream() - .filter(service -> service.getServiceId() == serviceNode.hashCode()) - .findFirst(); - if (openApiDefOpt.isEmpty()) { + Optional serviceNode = getServiceNode(memberNode, semanticModel); + if (serviceNode.isEmpty()) { updatedMembers.add(memberNode); continue; } - OpenApiDocContext.OpenApiDefinition openApiDef = openApiDefOpt.get(); - if (!openApiDef.isAutoEmbedToService()) { - updatedMembers.add(memberNode); - continue; - } - MetadataNode metadataNode = getMetadataNode(serviceNode); - MetadataNode.MetadataNodeModifier modifier = metadataNode.modify(); - NodeList updatedAnnotations = updateAnnotations( - metadataNode.annotations(), openApiDef.getDefinition(), context); - modifier.withAnnotations(updatedAnnotations); - MetadataNode updatedMetadataNode = modifier.apply(); - ServiceDeclarationNode.ServiceDeclarationNodeModifier serviceDecModifier = serviceNode.modify(); - serviceDecModifier.withMetadata(updatedMetadataNode); - ServiceDeclarationNode updatedServiceDecNode = serviceDecModifier.apply(); - updatedMembers.add(updatedServiceDecNode); + updateServiceDeclarationNode(openApi, context, serviceNode.get(), updatedMembers); } return AbstractNodeFactory.createNodeList(updatedMembers); } - private MetadataNode getMetadataNode(ServiceDeclarationNode serviceNode) { - return serviceNode.metadata().orElseGet(() -> { - NodeList annotations = NodeFactory.createNodeList(); - return NodeFactory.createMetadataNode(null, annotations); - }); + private void updateServiceDeclarationNode(List openApi, + SourceModifierContext context, ServiceNode serviceNode, + List updatedMembers) { + ModuleMemberDeclarationNode memberNode = serviceNode.getInternalNode(); + Optional openApiDefOpt = openApi.stream() + .filter(service -> service.getServiceId() == serviceNode.getServiceId()) + .findFirst(); + if (openApiDefOpt.isEmpty()) { + updatedMembers.add(memberNode); + return; + } + OpenApiDocContext.OpenApiDefinition openApiDef = openApiDefOpt.get(); + if (!openApiDef.isAutoEmbedToService()) { + updatedMembers.add(memberNode); + return; + } + NodeList existingAnnotations = serviceNode.metadata().map(MetadataNode::annotations) + .orElseGet(NodeFactory::createEmptyNodeList); + NodeList updatedAnnotations = updateAnnotations(existingAnnotations, + openApiDef.getDefinition(), context, serviceNode.kind().equals(ServiceNode.Kind.SERVICE_OBJECT_TYPE)); + serviceNode.updateAnnotations(updatedAnnotations); + updatedMembers.add(serviceNode.getInternalNode()); } private NodeList updateAnnotations(NodeList currentAnnotations, - String openApiDefinition, SourceModifierContext context) { + String openApiDef, SourceModifierContext context, + boolean isServiceContract) { NodeList updatedAnnotations = NodeFactory.createNodeList(); - boolean openApiAnnotationUpdated = false; + String annotationToBeUpdated = isServiceContract ? SERVICE_CONTRACT_INFO : SERVICE_CONFIG; + boolean annotationAlreadyExists = false; for (AnnotationNode annotation: currentAnnotations) { - if (isHttpServiceConfigAnnotation(annotation)) { - openApiAnnotationUpdated = true; - SeparatedNodeList updatedFields = getUpdatedFields(annotation, openApiDefinition, - context); + if (isHttpAnnotation(annotation, annotationToBeUpdated)) { + annotationAlreadyExists = true; + SeparatedNodeList updatedFields = getUpdatedFields(annotation, openApiDef, context); MappingConstructorExpressionNode annotationValue = NodeFactory.createMappingConstructorExpressionNode( NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN), updatedFields, @@ -186,8 +189,8 @@ private NodeList updateAnnotations(NodeList curr } updatedAnnotations = updatedAnnotations.add(annotation); } - if (!openApiAnnotationUpdated) { - AnnotationNode openApiAnnotation = getHttpServiceConfigAnnotation(openApiDefinition); + if (!annotationAlreadyExists) { + AnnotationNode openApiAnnotation = getHttpAnnotationWithOpenApi(annotationToBeUpdated, openApiDef); updatedAnnotations = updatedAnnotations.add(openApiAnnotation); } return updatedAnnotations; @@ -197,7 +200,7 @@ private SeparatedNodeList getUpdatedFields(AnnotationNode anno SourceModifierContext context) { Optional annotationValueOpt = annotation.annotValue(); if (annotationValueOpt.isEmpty()) { - return NodeFactory.createSeparatedNodeList(createOpenApiDefinitionField(servicePath)); + return NodeFactory.createSeparatedNodeList(createOpenApiDefinitionField(servicePath, false)); } List fields = new ArrayList<>(); MappingConstructorExpressionNode annotationValue = annotationValueOpt.get(); @@ -215,7 +218,7 @@ private SeparatedNodeList getUpdatedFields(AnnotationNode anno fields.add(field); fields.add(separator); } - fields.add(createOpenApiDefinitionField(servicePath)); + fields.add(createOpenApiDefinitionField(servicePath, false)); if (Objects.nonNull(openApiDefNode)) { context.reportDiagnostic(DiagnosticFactory.createDiagnostic( getDiagnosticInfo(HttpDiagnostic.HTTP_WARNING_102), openApiDefNode.location())); @@ -223,40 +226,43 @@ private SeparatedNodeList getUpdatedFields(AnnotationNode anno return NodeFactory.createSeparatedNodeList(fields); } - private AnnotationNode getHttpServiceConfigAnnotation(String openApiDefinition) { + private AnnotationNode getHttpAnnotationWithOpenApi(String annotationName, String openApiDefinition) { String configIdentifierString = Constants.HTTP_PACKAGE_NAME + SyntaxKind.COLON_TOKEN.stringValue() + - Constants.SERVICE_CONFIG_ANNOTATION_IDENTIFIER; + annotationName; IdentifierToken identifierToken = NodeFactory.createIdentifierToken(configIdentifierString); Token atToken = NodeFactory.createToken(SyntaxKind.AT_TOKEN); SimpleNameReferenceNode nameReferenceNode = NodeFactory.createSimpleNameReferenceNode(identifierToken); - MappingConstructorExpressionNode annotValue = getAnnotationExpression(openApiDefinition); + MappingConstructorExpressionNode annotValue = getAnnotationExpression(openApiDefinition, + annotationName.equals(SERVICE_CONTRACT_INFO)); return NodeFactory.createAnnotationNode(atToken, nameReferenceNode, annotValue); } - private MappingConstructorExpressionNode getAnnotationExpression(String openApiDefinition) { + private MappingConstructorExpressionNode getAnnotationExpression(String openApiDefinition, + boolean isServiceContract) { Token openBraceToken = NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN); Token closeBraceToken = NodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN); - SpecificFieldNode specificFieldNode = createOpenApiDefinitionField(openApiDefinition); + SpecificFieldNode specificFieldNode = createOpenApiDefinitionField(openApiDefinition, isServiceContract); SeparatedNodeList separatedNodeList = NodeFactory.createSeparatedNodeList(specificFieldNode); return NodeFactory.createMappingConstructorExpressionNode(openBraceToken, separatedNodeList, closeBraceToken); } - private static SpecificFieldNode createOpenApiDefinitionField(String openApiDefinition) { + private static SpecificFieldNode createOpenApiDefinitionField(String openApiDefinition, + boolean isServiceContract) { IdentifierToken fieldName = AbstractNodeFactory.createIdentifierToken(Constants.OPEN_API_DEFINITION_FIELD); Token colonToken = AbstractNodeFactory.createToken(SyntaxKind.COLON_TOKEN); String encodedValue = Base64.getEncoder().encodeToString(openApiDefinition.getBytes(Charset.defaultCharset())); - ExpressionNode expressionNode = NodeParser.parseExpression( - String.format("base64 `%s`.cloneReadOnly()", encodedValue)); + String format = isServiceContract ? "\"%s\"" : "base64 `%s`.cloneReadOnly()"; + ExpressionNode expressionNode = NodeParser.parseExpression(String.format(format, encodedValue)); return NodeFactory.createSpecificFieldNode(null, fieldName, colonToken, expressionNode); } - private boolean isHttpServiceConfigAnnotation(AnnotationNode annotationNode) { + private boolean isHttpAnnotation(AnnotationNode annotationNode, String annotationName) { if (!(annotationNode.annotReference() instanceof QualifiedNameReferenceNode referenceNode)) { return false; } if (!Constants.HTTP_PACKAGE_NAME.equals(referenceNode.modulePrefix().text())) { return false; } - return Constants.SERVICE_CONFIG_ANNOTATION_IDENTIFIER.equals(referenceNode.identifier().text()); + return annotationName.equals(referenceNode.identifier().text()); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java index f1c337e842..3b3a02cb4d 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java @@ -18,8 +18,8 @@ package io.ballerina.stdlib.http.compiler.codemodifier.oas.context; import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.openapi.service.mapper.model.ServiceNode; import io.ballerina.projects.DocumentId; import io.ballerina.projects.ModuleId; import io.ballerina.projects.Package; @@ -33,7 +33,7 @@ */ public class ServiceNodeAnalysisContext { - private final ServiceDeclarationNode node; + private final ServiceNode node; private final ModuleId moduleId; private final DocumentId documentId; private final SyntaxTree syntaxTree; @@ -43,7 +43,7 @@ public class ServiceNodeAnalysisContext { public ServiceNodeAnalysisContext(Package currentPackage, ModuleId moduleId, DocumentId documentId, SyntaxTree syntaxTree, SemanticModel semanticModel, - ServiceDeclarationNode node) { + ServiceNode node) { this.moduleId = moduleId; this.documentId = documentId; this.syntaxTree = syntaxTree; @@ -53,7 +53,7 @@ public ServiceNodeAnalysisContext(Package currentPackage, ModuleId moduleId, Doc this.node = node; } - public ServiceDeclarationNode node() { + public ServiceNode node() { return node; } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java index f8cacdc040..ec9dd7b63e 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java @@ -21,12 +21,11 @@ import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.compiler.syntax.tree.NodeLocation; -import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper; import io.ballerina.openapi.service.mapper.model.OASGenerationMetaInfo; import io.ballerina.openapi.service.mapper.model.OASResult; +import io.ballerina.openapi.service.mapper.model.ServiceNode; import io.ballerina.projects.Package; import io.ballerina.projects.Project; import io.ballerina.stdlib.http.compiler.HttpDiagnostic; @@ -34,6 +33,7 @@ import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.Location; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.OpenAPI; @@ -59,7 +59,7 @@ public AbstractOpenApiDocGenerator() { } @Override - public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, NodeLocation location) { + public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location) { try { int serviceId = config.getServiceId(); Package currentPackage = config.currentPackage(); @@ -68,15 +68,14 @@ public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context // find the project root path Path projectRoot = retrieveProjectRoot(srcRoot); - ServiceDeclarationNode serviceNode = config.serviceNode(); + ServiceNode serviceNode = config.serviceNode(); Optional serviceInfoAnnotationOpt = getServiceInfoAnnotation(serviceNode); if (serviceInfoAnnotationOpt.isPresent()) { AnnotationNode serviceInfoAnnotation = serviceInfoAnnotationOpt.get(); - boolean embed = retrieveValueForAnnotationFields( - serviceInfoAnnotation, Constants.EMBED) - .map(Boolean::parseBoolean) - .orElse(false); + boolean embed = retrieveValueForAnnotationFields(serviceInfoAnnotation, Constants.EMBED) + .map(Boolean::parseBoolean) + .orElse(false); // use the available open-api doc and update the context OpenApiContractResolver.ResolverResponse resolverResponse = this.contractResolver @@ -92,6 +91,9 @@ public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context // generate open-api doc and update the context if the `contract` configuration is not available generateOpenApiDoc(currentPackage.project(), config, context, location, embed); } + } else if (serviceNode.kind().equals(ServiceNode.Kind.SERVICE_OBJECT_TYPE)) { + // generate open-api doc and update the context if the service is a service contract type + generateOpenApiDoc(currentPackage.project(), config, context, location, true); } } catch (IOException | RuntimeException e) { // currently, we do not have open-api doc generation logic for following scenarios: @@ -108,13 +110,13 @@ private void updateOpenApiContext(ServiceNodeAnalysisContext context, int servic getContextHandler().updateContext(context.moduleId(), context.documentId(), openApiDef); } - private void updateCompilerContext(ServiceNodeAnalysisContext context, NodeLocation location, + private void updateCompilerContext(ServiceNodeAnalysisContext context, Location location, HttpDiagnostic errorCode) { Diagnostic diagnostic = getDiagnostics(errorCode, location); context.reportDiagnostic(diagnostic); } - private Optional getServiceInfoAnnotation(ServiceDeclarationNode serviceNode) { + private Optional getServiceInfoAnnotation(ServiceNode serviceNode) { Optional metadata = serviceNode.metadata(); if (metadata.isEmpty()) { return Optional.empty(); @@ -143,7 +145,7 @@ private Optional retrieveValueForAnnotationFields(AnnotationNode service } private void generateOpenApiDoc(Project project, OpenApiDocConfig config, ServiceNodeAnalysisContext context, - NodeLocation location, boolean embed) { + Location location, boolean embed) { if (!embed) { return; } @@ -151,7 +153,7 @@ private void generateOpenApiDoc(Project project, OpenApiDocConfig config, Servic String targetFile = String.format(FILE_NAME_FORMAT, serviceId); OASGenerationMetaInfo.OASGenerationMetaInfoBuilder builder = new OASGenerationMetaInfo.OASGenerationMetaInfoBuilder(); - builder.setServiceDeclarationNode(config.serviceNode()) + builder.setServiceNode(config.serviceNode()) .setSemanticModel(config.semanticModel()) .setOpenApiFileName(targetFile) .setBallerinaFilePath(null) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java index 2934d17ef4..43b385171c 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java @@ -17,8 +17,8 @@ */ package io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; -import io.ballerina.compiler.syntax.tree.NodeLocation; import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Location; import java.util.List; @@ -33,7 +33,7 @@ public DocGeneratorManager() { this.docGenerators = List.of(new SingleFileOpenApiDocGenerator(), new BalProjectOpenApiDocGenerator()); } - public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, NodeLocation location) { + public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location) { docGenerators.stream() .filter(dg -> dg.isSupported(config.projectType())) .findFirst() diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java index de7dd2cfd8..7fa6ddf1a2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java @@ -18,8 +18,8 @@ package io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SyntaxTree; +import io.ballerina.openapi.service.mapper.model.ServiceNode; import io.ballerina.projects.Package; import io.ballerina.projects.ProjectKind; @@ -33,9 +33,9 @@ * @param projectType project type */ public record OpenApiDocConfig(Package currentPackage, SemanticModel semanticModel, SyntaxTree syntaxTree, - ServiceDeclarationNode serviceNode, ProjectKind projectType) { + ServiceNode serviceNode, ProjectKind projectType) { public int getServiceId() { - return serviceNode.hashCode(); + return serviceNode.getServiceId(); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java index f9e3699565..6313ac807d 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java @@ -17,15 +17,15 @@ */ package io.ballerina.stdlib.http.compiler.codemodifier.oas.gen; -import io.ballerina.compiler.syntax.tree.NodeLocation; import io.ballerina.projects.ProjectKind; import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Location; /** * {@code Generator} generates open-api related docs for HTTP service. */ public interface OpenApiDocGenerator { - void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, NodeLocation location); + void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location); boolean isSupported(ProjectKind projectType); } diff --git a/gradle.properties b/gradle.properties index 6e2e3620ce..7ca6e39a61 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ lz4Version=1.3.0 marshallingVersion=2.0.5.Final protobufVersion=3.20.3 jacocoVersion=0.8.10 -ballerinaToOpenApiVersion=2.0.2 +ballerinaToOpenApiVersion=2.1.0-20240627-110644-38af277 swaggerVersion=2.2.9 stdlibIoVersion=1.6.0 From f2734bc75d46c54f0af0002e93578d3d4b25e425 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 1 Jul 2024 08:36:49 +0530 Subject: [PATCH 19/47] Fix openapi diagnostic filtering --- .../codemodifier/oas/gen/AbstractOpenApiDocGenerator.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java index ec9dd7b63e..047330d830 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java @@ -33,6 +33,7 @@ import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; import io.ballerina.tools.diagnostics.Diagnostic; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; import io.swagger.v3.core.util.Json; import io.swagger.v3.oas.models.OpenAPI; @@ -160,7 +161,9 @@ private void generateOpenApiDoc(Project project, OpenApiDocConfig config, Servic .setProject(project); OASResult oasResult = ServiceToOpenAPIMapper.generateOAS(builder.build()); Optional openApiOpt = oasResult.getOpenAPI(); - if (!oasResult.getDiagnostics().isEmpty() || openApiOpt.isEmpty()) { + if (oasResult.getDiagnostics().stream().noneMatch( + diagnostic -> diagnostic.getDiagnosticSeverity().equals(DiagnosticSeverity.ERROR)) + || openApiOpt.isEmpty()) { HttpDiagnostic errorCode = HttpDiagnostic.HTTP_WARNING_101; updateCompilerContext(context, location, errorCode); return; From e9adeabc212d7c42d5e4082053e89312bf7bb4e1 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 1 Jul 2024 08:37:20 +0530 Subject: [PATCH 20/47] Pack ballerina-to-openapi jar to compiler plugin bala --- ballerina/build.gradle | 2 ++ build-config/resources/CompilerPlugin.toml | 3 +++ compiler-plugin-tests/build.gradle | 1 + compiler-plugin/build.gradle | 16 +++++++++++++++- 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 8a3d06ae47..36617a6b8c 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -164,6 +164,7 @@ task updateTomlFiles { def stdlibDependentLz4Version = project.lz4Version def stdlibDependentMarshallingVersion = project.marshallingVersion def stdlibDependentProtobufVersion = project.protobufVersion + def ballerinaToOpenApiVersion = project.ballerinaToOpenApiVersion def newBallerinaToml = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) newBallerinaToml = newBallerinaToml.replace("@toml.version@", tomlVersion) @@ -183,6 +184,7 @@ task updateTomlFiles { ballerinaTomlFile.text = newBallerinaToml def newCompilerPluginToml = compilerPluginTomlFilePlaceHolder.text.replace("@project.version@", project.version) + newCompilerPluginToml = newCompilerPluginToml.replace("@ballerinaToOpenApiVersion.version@", ballerinaToOpenApiVersion) compilerPluginTomlFile.text = newCompilerPluginToml } } diff --git a/build-config/resources/CompilerPlugin.toml b/build-config/resources/CompilerPlugin.toml index 0f419b7db0..e822802983 100644 --- a/build-config/resources/CompilerPlugin.toml +++ b/build-config/resources/CompilerPlugin.toml @@ -4,3 +4,6 @@ class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" [[dependency]] path = "../compiler-plugin/build/libs/http-compiler-plugin-@project.version@.jar" + +[[dependency]] +path = "../compiler-plugin/build/libs/ballerina-to-openapi-@ballerinaToOpenApiVersion.version@.jar" diff --git a/compiler-plugin-tests/build.gradle b/compiler-plugin-tests/build.gradle index bda6b47a25..73c3f3cd25 100644 --- a/compiler-plugin-tests/build.gradle +++ b/compiler-plugin-tests/build.gradle @@ -101,3 +101,4 @@ jacocoTestReport { } test.dependsOn ":http-ballerina:build" +compileTestJava.dependsOn ":http-compiler-plugin:copyOpenApiJar" diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle index 87c9b8cc33..8998d1b357 100644 --- a/compiler-plugin/build.gradle +++ b/compiler-plugin/build.gradle @@ -24,6 +24,10 @@ plugins { description = 'Ballerina - HTTP Compiler Plugin' +configurations { + externalJars +} + dependencies { checkstyle project(':checkstyle') checkstyle "com.puppycrawl.tools:checkstyle:${puppycrawlCheckstyleVersion}" @@ -33,8 +37,9 @@ dependencies { implementation group: 'org.ballerinalang', name: 'ballerina-lang', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" - implementation group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" + + externalJars group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" } def excludePattern = '**/module-info.java' @@ -72,3 +77,12 @@ compileJava { classpath = files() } } + +task copyOpenApiJar(type: Copy) { + from { + configurations.externalJars.collect { it } + } + into "${buildDir}/libs" +} + +build.dependsOn copyOpenApiJar From ad6784b56c7844ef317c7dde79adf32caefb1f1b Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 1 Jul 2024 08:38:25 +0530 Subject: [PATCH 21/47] [Automated] Update the native jar versions --- ballerina/CompilerPlugin.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index fc0dcc7e77..7d7970c3ac 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -4,3 +4,6 @@ class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" [[dependency]] path = "../compiler-plugin/build/libs/http-compiler-plugin-2.12.0-SNAPSHOT.jar" + +[[dependency]] +path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.1.0-20240627-110644-38af277.jar" From 8137b996da0696545342d5704e5e0cd1582495ef Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Tue, 2 Jul 2024 15:17:00 +0530 Subject: [PATCH 22/47] Add compiler diagnostic tests --- .../http/compiler/CompilerPluginTest.java | 37 ++++++++++ .../compiler/CompilerPluginTestConstants.java | 8 ++ .../sample_package_40/Ballerina.toml | 4 + .../sample_package_40/service.bal | 74 +++++++++++++++++++ 4 files changed, 123 insertions(+) create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/service.bal diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java index e2f57d478f..fc8dc36ff0 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java @@ -60,6 +60,14 @@ import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_150; import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_151; import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_152; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_153; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_154; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_155; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_156; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_157; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_158; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_159; +import static io.ballerina.stdlib.http.compiler.CompilerPluginTestConstants.HTTP_160; /** * This class includes tests for Ballerina Http compiler plugin. @@ -875,4 +883,33 @@ public void testInterceptableServiceInterceptors() { Assert.assertFalse(diagnosticResult.hasWarnings()); Assert.assertFalse(diagnosticResult.hasErrors()); } + + @Test + public void testServiceContractValidations() { + Package currentPackage = loadPackage("sample_package_40"); + PackageCompilation compilation = currentPackage.getCompilation(); + DiagnosticResult diagnosticResult = compilation.diagnosticResult(); + Assert.assertEquals(diagnosticResult.errorCount(), 8); + assertError(diagnosticResult, 0, "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", HTTP_154); + assertError(diagnosticResult, 1, "'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", HTTP_153); + assertError(diagnosticResult, 2, "configuring base path in the 'http:ServiceConfig' annotation" + + " is not allowed for non service contract types", HTTP_155); + assertError(diagnosticResult, 3, "invalid service type descriptor found in 'http:ServiceConfig' " + + "annotation. Expected service type: 'ContractService' but found: 'ContractServiceWithoutServiceConfig'", + HTTP_156); + assertError(diagnosticResult, 4, "'serviceType' is not allowed in the service which is not implemented" + + " via the 'http:ServiceContract' type", HTTP_157); + assertError(diagnosticResult, 5, "resource function which is not defined in the service contract type:" + + " 'ContractServiceWithResource', is not allowed", HTTP_158); + assertError(diagnosticResult, 6, "'http:ResourceConfig' annotation is not allowed for resource function " + + "implemented via the 'http:ServiceContract' type. The HTTP annotations are inferred from the service" + + " contract type", HTTP_159); + assertError(diagnosticResult, 7, "'http:Header' annotation is not allowed for resource function implemented" + + " via the 'http:ServiceContract' type. The HTTP annotations are inferred from the service contract" + + " type", HTTP_160); + } } diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java index 555781b659..a48c65c936 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java @@ -75,4 +75,12 @@ private CompilerPluginTestConstants() {} public static final String HTTP_150 = "HTTP_150"; public static final String HTTP_151 = "HTTP_151"; public static final String HTTP_152 = "HTTP_152"; + public static final String HTTP_153 = "HTTP_153"; + public static final String HTTP_154 = "HTTP_154"; + public static final String HTTP_155 = "HTTP_155"; + public static final String HTTP_156 = "HTTP_156"; + public static final String HTTP_157 = "HTTP_157"; + public static final String HTTP_158 = "HTTP_158"; + public static final String HTTP_159 = "HTTP_159"; + public static final String HTTP_160 = "HTTP_160"; } diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/Ballerina.toml b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/Ballerina.toml new file mode 100644 index 0000000000..b7b8157477 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "http_test" +name = "sample_40" +version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/service.bal b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/service.bal new file mode 100644 index 0000000000..6cfcbdb41c --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_40/service.bal @@ -0,0 +1,74 @@ +import ballerina/http; + +type ContractServiceWithoutServiceConfig service object { + *http:ServiceContract; +}; + +@http:ServiceConfig { + basePath: "/api" +} +type ContractService service object { + *http:ServiceContract; +}; + +@http:ServiceConfig { +} +service ContractService /api on new http:Listener(9090) { +}; + +@http:ServiceConfig { + serviceType: ContractService +} +service ContractService on new http:Listener(9090) { +}; + +@http:ServiceConfig { + basePath: "/api" +} +type NonContractService service object { + *http:Service; +}; + +@http:ServiceConfig { + serviceType: ContractServiceWithoutServiceConfig +} +service ContractService on new http:Listener(9090) { +}; + +@http:ServiceConfig { + serviceType: ContractServiceWithoutServiceConfig +} +service NonContractService on new http:Listener(9090) { +}; + + +@http:ServiceConfig { + basePath: "/api" +} +type ContractServiceWithResource service object { + *http:ServiceContract; + + resource function get greeting(@http:Header string? header) returns string; +}; + +service ContractServiceWithResource on new http:Listener(9090) { + resource function get greeting(string? header) returns string { + return "Hello, World!"; + } +}; + +service ContractServiceWithResource on new http:Listener(9090) { + resource function get greeting(string? header) returns string { + return "Hello, World!"; + } + + resource function get newGreeting() {} +}; + +service ContractServiceWithResource on new http:Listener(9090) { + + @http:ResourceConfig {} + resource function get greeting(@http:Header string? header) returns string { + return "Hello, World!"; + } +}; From bb486ddd1c6bdfd262dfab1f94603801b86da78a Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 5 Jul 2024 07:55:17 +0530 Subject: [PATCH 23/47] Fix compiler diagnostic issue --- .../stdlib/http/compiler/HttpDiagnostic.java | 3 ++- .../codeaction/ImplementServiceContract.java | 25 ++++++++++++++++--- .../oas/gen/AbstractOpenApiDocGenerator.java | 2 +- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnostic.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnostic.java index b35b5018f6..6beff9f045 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnostic.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnostic.java @@ -125,7 +125,8 @@ public enum HttpDiagnostic { 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_WARNING_101("HTTP_WARNING_101", "generated open-api definition is empty", WARNING), + HTTP_WARNING_101("HTTP_WARNING_101", "generated open-api definition is empty due to the errors " + + "in the generation", WARNING), HTTP_WARNING_102("HTTP_WARNING_102", "The openapi definition is overridden by the `embed: true` option", WARNING), HTTP_HINT_101("HTTP_HINT_101", "Payload annotation can be added", INTERNAL), diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java index 03527f6a05..5f03c7ec96 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContract.java @@ -19,6 +19,7 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.MethodSymbol; +import io.ballerina.compiler.api.symbols.ModuleSymbol; import io.ballerina.compiler.api.symbols.ObjectTypeSymbol; import io.ballerina.compiler.api.symbols.ResourceMethodSymbol; import io.ballerina.compiler.api.symbols.Symbol; @@ -48,6 +49,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -77,7 +79,7 @@ public Optional codeActionInfo(CodeActionContext context) { return Optional.empty(); } - return getCodeActionInfoWithLocation(node, "Implement all the resource methods from service contract"); + return getCodeActionInfoWithLocation(node, "Implement service contract resources"); } @Override @@ -128,6 +130,7 @@ public List execute(CodeActionExecutionContext context) { } Map methodSymbolMap = serviceObjTypeSymbol.methods(); + String parentModuleName = getParentModuleName(semanticModel, (ServiceDeclarationNode) node); StringBuilder methods = new StringBuilder(); for (Map.Entry entry : methodSymbolMap.entrySet()) { if (existingMethods.contains(entry.getKey())) { @@ -135,7 +138,7 @@ public List execute(CodeActionExecutionContext context) { } MethodSymbol methodSymbol = entry.getValue(); if (methodSymbol instanceof ResourceMethodSymbol resourceMethodSymbol) { - methods.append(getMethodSignature(resourceMethodSymbol)); + methods.append(getMethodSignature(resourceMethodSymbol, parentModuleName)); } } @@ -148,8 +151,12 @@ public List execute(CodeActionExecutionContext context) { return Collections.singletonList(new DocumentEdit(context.fileUri(), SyntaxTree.from(modifiedTextDocument))); } - private String getMethodSignature(ResourceMethodSymbol resourceMethodSymbol) { - return LS + "\t" + sanitizePackageNames(resourceMethodSymbol.signature()) + " {" + LS + LS + "\t}" + LS; + private String getMethodSignature(ResourceMethodSymbol resourceMethodSymbol, String parentModuleName) { + String resourceSignature = resourceMethodSymbol.signature(); + if (Objects.nonNull(parentModuleName)) { + resourceSignature = resourceSignature.replace(parentModuleName + ":", ""); + } + return LS + "\t" + sanitizePackageNames(resourceSignature) + " {" + LS + LS + "\t}" + LS; } private String sanitizePackageNames(String input) { @@ -158,6 +165,16 @@ private String sanitizePackageNames(String input) { return matcher.replaceAll("$2"); } + private String getParentModuleName(SemanticModel semanticModel, ServiceDeclarationNode serviceDeclarationNode) { + Optional serviceDeclarationSymbol = semanticModel.symbol(serviceDeclarationNode); + if (serviceDeclarationSymbol.isEmpty()) { + return null; + } + + Optional module = serviceDeclarationSymbol.get().getModule(); + return module.map(moduleSymbol -> moduleSymbol.id().toString()).orElse(null); + } + @Override public String name() { return "IMPLEMENT_SERVICE_CONTRACT"; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java index 047330d830..016ad4f7cd 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java @@ -161,7 +161,7 @@ private void generateOpenApiDoc(Project project, OpenApiDocConfig config, Servic .setProject(project); OASResult oasResult = ServiceToOpenAPIMapper.generateOAS(builder.build()); Optional openApiOpt = oasResult.getOpenAPI(); - if (oasResult.getDiagnostics().stream().noneMatch( + if (oasResult.getDiagnostics().stream().anyMatch( diagnostic -> diagnostic.getDiagnosticSeverity().equals(DiagnosticSeverity.ERROR)) || openApiOpt.isEmpty()) { HttpDiagnostic errorCode = HttpDiagnostic.HTTP_WARNING_101; From c1201044c8ec5b5f60e675eabea8ca3f111775d9 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 15 Jul 2024 09:50:19 +0530 Subject: [PATCH 24/47] Update lang version --- build-config/resources/Ballerina.toml | 2 +- gradle.properties | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index efc9e20756..2bb22346b2 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -7,7 +7,7 @@ keywords = ["http", "network", "service", "listener", "client"] repository = "https://github.com/ballerina-platform/module-ballerina-http" icon = "icon.png" license = ["Apache-2.0"] -distribution = "2201.9.0" +distribution = "2201.10.0" export = ["http", "http.httpscerr"] [platform.java17] diff --git a/gradle.properties b/gradle.properties index 7ca6e39a61..d053a153ec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.stdlib version=2.12.0-SNAPSHOT -ballerinaLangVersion=2201.9.0 +ballerinaLangVersion=2201.10.0-20240711-141800-6245661f ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 nettyVersion=4.1.108.Final @@ -23,7 +23,7 @@ lz4Version=1.3.0 marshallingVersion=2.0.5.Final protobufVersion=3.20.3 jacocoVersion=0.8.10 -ballerinaToOpenApiVersion=2.1.0-20240627-110644-38af277 +ballerinaToOpenApiVersion=2.1.0-20240715-091523-93f4d1b swaggerVersion=2.2.9 stdlibIoVersion=1.6.0 From 08912999ed9e16ee7b76fb2ca534cbd81a25f0da Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 15 Jul 2024 09:51:20 +0530 Subject: [PATCH 25/47] [Automated] Update the native jar versions --- ballerina/Ballerina.toml | 2 +- ballerina/CompilerPlugin.toml | 2 +- ballerina/Dependencies.toml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index b85b6b35d5..e6117c197a 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -7,7 +7,7 @@ keywords = ["http", "network", "service", "listener", "client"] repository = "https://github.com/ballerina-platform/module-ballerina-http" icon = "icon.png" license = ["Apache-2.0"] -distribution = "2201.9.0" +distribution = "2201.10.0" export = ["http", "http.httpscerr"] [platform.java17] diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index 7d7970c3ac..53f33b5703 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -6,4 +6,4 @@ class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" path = "../compiler-plugin/build/libs/http-compiler-plugin-2.12.0-SNAPSHOT.jar" [[dependency]] -path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.1.0-20240627-110644-38af277.jar" +path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.1.0-20240715-091523-93f4d1b.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 7bfd3e4a76..e1b07647c4 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -128,7 +128,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, From 16258e9dc1e782be654efc5c2ac8ab48b5a0d9be Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 15 Jul 2024 10:01:55 +0530 Subject: [PATCH 26/47] [Automated] Update the native jar versions --- ballerina-tests/http-advanced-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-client-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-dispatching-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-interceptor-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-misc-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-resiliency-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-security-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-service-tests/Dependencies.toml | 4 ++-- ballerina-tests/http-test-common/Dependencies.toml | 2 +- ballerina-tests/http2-tests/Dependencies.toml | 4 ++-- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index 69c0e154ce..0675047570 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -160,7 +160,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index 1c212697e9..2d92732bb6 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index 00b735a6ad..c46c3c2f07 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -159,7 +159,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index d4ab93c642..752e593483 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -147,7 +147,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index 0c5d84772a..02586a163c 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -156,7 +156,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index 246afea60e..1169612507 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -154,7 +154,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 5dc9eda59d..cc2dc1ee1d 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -152,7 +152,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index 745986bd92..0524b04fdb 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-test-common/Dependencies.toml b/ballerina-tests/http-test-common/Dependencies.toml index ac897d2d87..cdd739c784 100644 --- a/ballerina-tests/http-test-common/Dependencies.toml +++ b/ballerina-tests/http-test-common/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index 506471193d..a6c1478a05 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.9.0" +distribution-version = "2201.10.0-20240711-141800-6245661f" [[package]] org = "ballerina" @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.11.0" +version = "2.12.1" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, From bec76254eac128c274b103e292a983c6f894815c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 15 Jul 2024 10:05:38 +0530 Subject: [PATCH 27/47] Refactor service contract implementation with static resource --- ballerina/http_annotation.bal | 13 +- compiler-plugin/spotbugs-exclude.xml | 10 + .../http/compiler/HttpCompilerPlugin.java | 7 +- .../http/compiler/HttpCompilerPluginUtil.java | 78 +++++ .../http/compiler/HttpServiceAnalyzer.java | 19 +- .../compiler/HttpServiceObjTypeAnalyzer.java | 36 +-- .../http/compiler/HttpServiceValidator.java | 82 ++---- .../compiler/ServiceContractOasGenerator.java | 71 +++++ .../http/compiler/ServiceOasGenerator.java | 184 ++++++++++++ .../codemodifier/HttpServiceModifier.java | 10 +- .../ContractInfoModifierTask.java} | 57 +++- .../compiler/codemodifier/oas/Constants.java | 40 --- .../codemodifier/oas/DocGenerationUtils.java | 36 --- .../oas/HttpServiceAnalysisTask.java | 47 --- .../oas/OpenApiInfoUpdaterTask.java | 268 ------------------ .../oas/context/OpenApiDocContext.java | 83 ------ .../oas/context/OpenApiDocContextHandler.java | 91 ------ .../context/ServiceNodeAnalysisContext.java | 87 ------ .../oas/gen/AbstractOpenApiDocGenerator.java | 182 ------------ .../gen/BalProjectOpenApiDocGenerator.java | 30 -- .../oas/gen/DocGeneratorManager.java | 42 --- .../oas/gen/OpenApiContractResolver.java | 128 --------- .../oas/gen/OpenApiDocConfig.java | 41 --- .../oas/gen/OpenApiDocGenerator.java | 31 -- .../gen/SingleFileOpenApiDocGenerator.java | 54 ---- .../payload/HttpPayloadParamIdentifier.java | 4 +- .../PayloadAnnotationModifierTask.java | 2 +- .../stdlib/http/api/HttpService.java | 35 +++ .../http/api/HttpServiceFromContract.java | 1 + 29 files changed, 490 insertions(+), 1279 deletions(-) create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/{ServiceTypeModifierTask.java => contract/ContractInfoModifierTask.java} (72%) delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index a24729de03..d22a63c7f2 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -35,6 +35,7 @@ public type HttpServiceConfig record {| ListenerAuthConfig[] auth?; string mediaTypeSubtypePrefix?; boolean treatNilableAsOptional = true; + @deprecated byte[] openApiDefinition = []; boolean validation = true; typedesc serviceType?; @@ -156,15 +157,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; - -# Defines the information about the service contract. -# -# + openApiDefinition - The generated OpenAPI definition for the HTTP service. This is auto-generated at -# compile-time by default -public type ServiceContractInformation record {| - string openApiDefinition; -|}; - -# The annotation which is used to define the information about the service contract. This annotation is auto-generated -# at compile-time by default. Adding this annotation manually is not recommended. -public const annotation ServiceContractInformation ServiceContractInfo on type; diff --git a/compiler-plugin/spotbugs-exclude.xml b/compiler-plugin/spotbugs-exclude.xml index 5ec731368d..0bdc8fcdd7 100644 --- a/compiler-plugin/spotbugs-exclude.xml +++ b/compiler-plugin/spotbugs-exclude.xml @@ -81,4 +81,14 @@ + + + + + + + + + + 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 b6f829aa90..5936bfa6a1 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 @@ -37,6 +37,7 @@ import io.ballerina.stdlib.http.compiler.completion.HttpServiceBodyContextProvider; import java.util.List; +import java.util.Map; /** * The compiler plugin implementation for Ballerina Http package. @@ -45,8 +46,10 @@ public class HttpCompilerPlugin extends CompilerPlugin { @Override public void init(CompilerPluginContext context) { - context.addCodeModifier(new HttpServiceModifier()); - context.addCodeAnalyzer(new HttpServiceAnalyzer()); + Map ctxData = context.userData(); + ctxData.put("HTTP_CODE_MODIFIER_EXECUTED", false); + context.addCodeModifier(new HttpServiceModifier(ctxData)); + context.addCodeAnalyzer(new HttpServiceAnalyzer(ctxData)); getCodeActions().forEach(context::addCodeAction); getCompletionProviders().forEach(context::addCompletionProvider); } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java index 291ade6422..2db9d66682 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java @@ -18,24 +18,33 @@ package io.ballerina.stdlib.http.compiler; +import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.Types; import io.ballerina.compiler.api.symbols.FunctionSymbol; import io.ballerina.compiler.api.symbols.FunctionTypeSymbol; import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol; import io.ballerina.compiler.api.symbols.ModuleSymbol; +import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; import io.ballerina.compiler.api.symbols.TypeDescKind; +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.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ReturnTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticFactory; import io.ballerina.tools.diagnostics.DiagnosticInfo; import io.ballerina.tools.diagnostics.DiagnosticProperty; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; import java.util.HashMap; @@ -59,6 +68,7 @@ import static io.ballerina.stdlib.http.compiler.Constants.FLOAT_ARRAY; import static io.ballerina.stdlib.http.compiler.Constants.HEADER_OBJ_NAME; import static io.ballerina.stdlib.http.compiler.Constants.HTTP; +import static io.ballerina.stdlib.http.compiler.Constants.HTTP_SERVICE_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.INT; import static io.ballerina.stdlib.http.compiler.Constants.INTERCEPTOR_RESOURCE_RETURN_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.INT_ARRAY; @@ -309,4 +319,72 @@ private static void populateNilableBasicArrayTypes(Map typeS typeSymbols.put(NILABLE_MAP_OF_ANYDATA_ARRAY, types.builder().UNION_TYPE.withMemberTypes( typeSymbols.get(ARRAY_OF_MAP_OF_ANYDATA), types.NIL).build()); } + + public static boolean diagnosticContainsErrors(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { + List diagnostics = syntaxNodeAnalysisContext.semanticModel().diagnostics(); + return diagnostics.stream() + .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); + } + + public static ServiceDeclarationNode getServiceDeclarationNode(SyntaxNodeAnalysisContext context) { + if (!(context.node() instanceof ServiceDeclarationNode serviceDeclarationNode)) { + return null; + } + return getServiceDeclarationNode(serviceDeclarationNode, context.semanticModel()); + } + + public static ServiceDeclarationNode getServiceDeclarationNode(Node node, SemanticModel semanticModel) { + if (!(node instanceof ServiceDeclarationNode serviceDeclarationNode)) { + return null; + } + + Optional serviceSymOptional = semanticModel.symbol(node); + if (serviceSymOptional.isPresent()) { + List listenerTypes = ((ServiceDeclarationSymbol) serviceSymOptional.get()).listenerTypes(); + if (listenerTypes.stream().noneMatch(HttpCompilerPluginUtil::isListenerBelongsToHttpModule)) { + return null; + } + } + return serviceDeclarationNode; + } + + private static boolean isListenerBelongsToHttpModule(TypeSymbol listenerType) { + if (listenerType.typeKind() == TypeDescKind.UNION) { + return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() + .filter(typeDescriptor -> typeDescriptor instanceof TypeReferenceTypeSymbol) + .map(typeReferenceTypeSymbol -> (TypeReferenceTypeSymbol) typeReferenceTypeSymbol) + .anyMatch(typeReferenceTypeSymbol -> isHttpModule(typeReferenceTypeSymbol.getModule().get())); + } + + if (listenerType.typeKind() == TypeDescKind.TYPE_REFERENCE) { + return isHttpModule(((TypeReferenceTypeSymbol) listenerType).typeDescriptor().getModule().get()); + } + return false; + } + + public static boolean isServiceObjectType(ObjectTypeDescriptorNode typeNode) { + return typeNode.objectTypeQualifiers().stream().anyMatch( + qualifier -> qualifier.kind().equals(SyntaxKind.SERVICE_KEYWORD)); + } + + public static boolean isHttpServiceType(SemanticModel semanticModel, Node typeNode) { + if (!(typeNode instanceof ObjectTypeDescriptorNode serviceObjType) || !isServiceObjectType(serviceObjType)) { + return false; + } + + Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); + if (serviceObjSymbol.isEmpty() || + (!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) { + return false; + } + + Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, + HTTP_SERVICE_TYPE); + if (serviceContractType.isEmpty() || + !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { + return false; + } + + return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java index 4bf17a52ab..83504cb8f5 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java @@ -22,15 +22,32 @@ import io.ballerina.projects.plugins.CodeAnalysisContext; import io.ballerina.projects.plugins.CodeAnalyzer; +import java.util.Map; + /** * The {@code CodeAnalyzer} for Ballerina Http services. */ public class HttpServiceAnalyzer extends CodeAnalyzer { + private final Map ctxData; + + public HttpServiceAnalyzer(Map ctxData) { + this.ctxData = ctxData; + } + @Override public void init(CodeAnalysisContext codeAnalysisContext) { codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpServiceObjTypeAnalyzer(), SyntaxKind.OBJECT_TYPE_DESC); codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpServiceValidator(), SyntaxKind.SERVICE_DECLARATION); + + boolean httpCodeModifierExecuted = (boolean) ctxData.getOrDefault("HTTP_CODE_MODIFIER_EXECUTED", false); + if (httpCodeModifierExecuted) { + codeAnalysisContext.addSyntaxNodeAnalysisTask(new ServiceContractOasGenerator(), + SyntaxKind.OBJECT_TYPE_DESC); + codeAnalysisContext.addSyntaxNodeAnalysisTask(new ServiceOasGenerator(), + SyntaxKind.SERVICE_DECLARATION); + } + codeAnalysisContext.addSyntaxNodeAnalysisTask(new HttpInterceptorServiceValidator(), - SyntaxKind.CLASS_DEFINITION); + SyntaxKind.CLASS_DEFINITION); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java index b34236dde3..38f611dd7b 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceObjTypeAnalyzer.java @@ -24,20 +24,17 @@ import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; -import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import java.util.List; import java.util.Optional; import static io.ballerina.stdlib.http.compiler.Constants.BALLERINA; import static io.ballerina.stdlib.http.compiler.Constants.EMPTY; import static io.ballerina.stdlib.http.compiler.Constants.HTTP; -import static io.ballerina.stdlib.http.compiler.Constants.HTTP_SERVICE_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONTRACT_TYPE; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; /** * Validates the HTTP service object type. @@ -48,8 +45,7 @@ public class HttpServiceObjTypeAnalyzer extends HttpServiceValidator { @Override public void perform(SyntaxNodeAnalysisContext context) { - List diagnostics = context.semanticModel().diagnostics(); - if (diagnostics.stream().anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity()))) { + if (diagnosticContainsErrors(context)) { return; } @@ -67,32 +63,6 @@ public void perform(SyntaxNodeAnalysisContext context) { validateResources(context, members); } - public static boolean isServiceObjectType(ObjectTypeDescriptorNode typeNode) { - return typeNode.objectTypeQualifiers().stream().anyMatch( - qualifier -> qualifier.kind().equals(SyntaxKind.SERVICE_KEYWORD)); - } - - public static boolean isHttpServiceType(SemanticModel semanticModel, Node typeNode) { - if (!(typeNode instanceof ObjectTypeDescriptorNode serviceObjType) || !isServiceObjectType(serviceObjType)) { - return false; - } - - Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); - if (serviceObjSymbol.isEmpty() || - (!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) { - return false; - } - - Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, - HTTP_SERVICE_TYPE); - if (serviceContractType.isEmpty() || - !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { - return false; - } - - return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); - } - private static boolean isServiceContractType(SemanticModel semanticModel, ObjectTypeDescriptorNode serviceObjType) { Optional serviceObjSymbol = semanticModel.symbol(serviceObjType.parent()); 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 72cf3c89e5..de39ea5810 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 @@ -20,13 +20,10 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.ObjectTypeSymbol; -import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.Symbol; import io.ballerina.compiler.api.symbols.TypeDefinitionSymbol; -import io.ballerina.compiler.api.symbols.TypeDescKind; 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.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; @@ -45,7 +42,6 @@ import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticFactory; import io.ballerina.tools.diagnostics.DiagnosticInfo; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; import org.wso2.ballerinalang.compiler.diagnostic.BLangDiagnosticLocation; @@ -71,8 +67,9 @@ import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_TYPE; 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.diagnosticContainsErrors; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getCtxTypes; -import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpModule; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getServiceDeclarationNode; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.updateDiagnostic; import static io.ballerina.stdlib.http.compiler.HttpDiagnostic.HTTP_101; import static io.ballerina.stdlib.http.compiler.HttpDiagnostic.HTTP_119; @@ -255,48 +252,6 @@ private static void checkForServiceImplementationErrors(SyntaxNodeAnalysisContex } } - public static boolean diagnosticContainsErrors(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { - List diagnostics = syntaxNodeAnalysisContext.semanticModel().diagnostics(); - return diagnostics.stream() - .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); - } - - public static ServiceDeclarationNode getServiceDeclarationNode(SyntaxNodeAnalysisContext context) { - if (!(context.node() instanceof ServiceDeclarationNode serviceDeclarationNode)) { - return null; - } - return getServiceDeclarationNode(serviceDeclarationNode, context.semanticModel()); - } - - public static ServiceDeclarationNode getServiceDeclarationNode(Node node, SemanticModel semanticModel) { - if (!(node instanceof ServiceDeclarationNode serviceDeclarationNode)) { - return null; - } - - Optional serviceSymOptional = semanticModel.symbol(node); - if (serviceSymOptional.isPresent()) { - List listenerTypes = ((ServiceDeclarationSymbol) serviceSymOptional.get()).listenerTypes(); - if (listenerTypes.stream().noneMatch(HttpServiceValidator::isListenerBelongsToHttpModule)) { - return null; - } - } - return serviceDeclarationNode; - } - - private static boolean isListenerBelongsToHttpModule(TypeSymbol listenerType) { - if (listenerType.typeKind() == TypeDescKind.UNION) { - return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() - .filter(typeDescriptor -> typeDescriptor instanceof TypeReferenceTypeSymbol) - .map(typeReferenceTypeSymbol -> (TypeReferenceTypeSymbol) typeReferenceTypeSymbol) - .anyMatch(typeReferenceTypeSymbol -> isHttpModule(typeReferenceTypeSymbol.getModule().get())); - } - - if (listenerType.typeKind() == TypeDescKind.TYPE_REFERENCE) { - return isHttpModule(((TypeReferenceTypeSymbol) listenerType).typeDescriptor().getModule().get()); - } - return false; - } - private static void validateResourceLinks(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, LinksMetaData linksMetaData) { if (!linksMetaData.hasNameReferenceObjects()) { @@ -375,28 +330,31 @@ private static void validateAnnotationUsageForServiceContractType(SyntaxNodeAnal AnnotationNode annotation, MappingConstructorExpressionNode annotValue, TypeDescriptorNode typeDescriptorNode) { - if (Objects.isNull(annotValue) || annotValue.fields().isEmpty() || annotValue.fields().size() > 1) { + // TODO: Change annotValue.fields().size() > 1 after resource migration + if (Objects.isNull(annotValue) || annotValue.fields().isEmpty() || annotValue.fields().size() > 2) { reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); return; } - MappingFieldNode field = annotValue.fields().get(0); - String fieldString = field.toString(); - fieldString = fieldString.trim().replaceAll(UNNECESSARY_CHARS_REGEX, ""); - if (field.kind().equals(SyntaxKind.SPECIFIC_FIELD)) { - String[] strings = fieldString.split(COLON, 2); - if (SERVICE_TYPE.equals(strings[0].trim())) { - String expectedServiceType = typeDescriptorNode.toString().trim(); - String actualServiceType = strings[1].trim(); - if (!actualServiceType.equals(expectedServiceType)) { - reportInvalidServiceContractType(ctx, expectedServiceType, actualServiceType, - annotation.location()); + for (MappingFieldNode field : annotValue.fields()) { + String fieldString = field.toString(); + fieldString = fieldString.trim().replaceAll(UNNECESSARY_CHARS_REGEX, ""); + if (field.kind().equals(SyntaxKind.SPECIFIC_FIELD)) { + String[] strings = fieldString.split(COLON, 2); + if (SERVICE_TYPE.equals(strings[0].trim())) { + String expectedServiceType = typeDescriptorNode.toString().trim(); + String actualServiceType = strings[1].trim(); + if (!actualServiceType.equals(expectedServiceType)) { + reportInvalidServiceContractType(ctx, expectedServiceType, actualServiceType, + annotation.location()); + return; + } + } else if (!("openApiDefinition".equals(strings[0].trim()))) { + reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); + return; } - return; } } - - reportInvalidServiceConfigAnnotationUsage(ctx, annotation.location()); } protected static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext ctx, diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java new file mode 100644 index 0000000000..a833d8228a --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java @@ -0,0 +1,71 @@ +/* + * 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; + +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.TypeDefinitionNode; +import io.ballerina.openapi.service.mapper.model.ServiceContractType; +import io.ballerina.openapi.service.mapper.model.ServiceNode; +import io.ballerina.projects.ProjectKind; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; + +import java.util.Optional; + +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; + +/** + * This class generates the OpenAPI definition resource for the service contract type node. + * + * @since 2.12.0 + */ +public class ServiceContractOasGenerator extends ServiceOasGenerator { + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + if (diagnosticContainsErrors(context) || + !context.currentPackage().project().kind().equals(ProjectKind.BUILD_PROJECT)) { + return; + } + + Node typeNode = context.node(); + if (!isHttpServiceType(context.semanticModel(), typeNode)) { + return; + } + + ObjectTypeDescriptorNode serviceObjectType = (ObjectTypeDescriptorNode) typeNode; + TypeDefinitionNode serviceTypeNode = (TypeDefinitionNode) serviceObjectType.parent(); + + if (!serviceTypeNode.visibilityQualifier() + .map(qualifier -> qualifier.text().equals("public")) + .orElse(false)) { + return; + } + + String fileName = String.format("%s.json", serviceTypeNode.typeName().text()); + ServiceNode serviceNode = new ServiceContractType(serviceTypeNode); + Optional openApi = generateOpenApi(fileName, context.currentPackage().project(), + context.semanticModel(), serviceNode); + if (openApi.isEmpty()) { + return; + } + + writeOpenApiAsTargetResource(context.currentPackage().project(), fileName, openApi.get()); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java new file mode 100644 index 0000000000..701bf8e44a --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java @@ -0,0 +1,184 @@ +/* + * 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; + +import io.ballerina.compiler.api.SemanticModel; +import io.ballerina.compiler.api.symbols.Symbol; +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MetadataNode; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper; +import io.ballerina.openapi.service.mapper.model.OASGenerationMetaInfo; +import io.ballerina.openapi.service.mapper.model.OASResult; +import io.ballerina.openapi.service.mapper.model.ServiceDeclaration; +import io.ballerina.openapi.service.mapper.model.ServiceNode; +import io.ballerina.projects.Project; +import io.ballerina.projects.plugins.AnalysisTask; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.tools.diagnostics.DiagnosticSeverity; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.oas.models.OpenAPI; + +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; + +import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.normalizeTitle; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getServiceDeclarationNode; + +/** + * This class generates the OpenAPI definition resource for the service declaration node. + * + * @since 2.12.0 + */ +public class ServiceOasGenerator implements AnalysisTask { + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + if (diagnosticContainsErrors(context)) { + return; + } + + ServiceDeclarationNode serviceDeclarationNode = getServiceDeclarationNode(context); + if (serviceDeclarationNode == null) { + return; + } + + Optional serviceInfoAnnotation = getServiceInfoAnnotation(serviceDeclarationNode); + if (serviceInfoAnnotation.isEmpty()) { + return; + } + + boolean embedOpenAPI = retrieveValueFromAnnotation(serviceInfoAnnotation.get(), "embed") + .map(Boolean::parseBoolean) + .orElse(false); + if (!embedOpenAPI) { + return; + } + + Optional contractPath = retrieveValueFromAnnotation(serviceInfoAnnotation.get(), "contract"); + if (contractPath.isPresent()) { + return; + } + + Optional symbol = context.semanticModel().symbol(serviceDeclarationNode); + if (symbol.isEmpty()) { + // Add warning diagnostic + return; + } + String fileName = getFileName(symbol.get().hashCode()); + + + ServiceNode serviceNode = new ServiceDeclaration(serviceDeclarationNode, context.semanticModel()); + Optional openApi = generateOpenApi(fileName, context.currentPackage().project(), + context.semanticModel(), serviceNode); + if (openApi.isEmpty()) { + return; + } + + writeOpenApiAsTargetResource(context.currentPackage().project(), fileName, openApi.get()); + } + + protected static String getFileName(int hashCode) { + String hashString = Integer.toString(hashCode); + return String.format("openapi_%s.json", + hashString.startsWith("-") ? "0" + hashString.substring(1) : hashString); + } + + protected static void writeOpenApiAsTargetResource(Project project, String fileName, String openApi) { + Path targetPath = project.targetDir(); + // Create a folder resources if not exists + Path resourcesPath = targetPath.resolve("resources"); + if (!resourcesPath.toFile().exists()) { + try { + Files.createDirectory(resourcesPath); + } catch (IOException e) { + // Add warning diagnostic + return; + } + } + writeFile(fileName, openApi, resourcesPath); + } + + protected static void writeFile(String fileName, String content, Path dirPath) { + Path openApiPath = dirPath.resolve(fileName); + try (FileWriter writer = new FileWriter(openApiPath.toString(), StandardCharsets.UTF_8)) { + writer.write(content); + } catch (IOException e) { + // Add warning diagnostic + } + } + + private Optional getServiceInfoAnnotation(ServiceDeclarationNode serviceDeclarationNode) { + Optional metadata = serviceDeclarationNode.metadata(); + if (metadata.isEmpty()) { + return Optional.empty(); + } + MetadataNode metaData = metadata.get(); + NodeList annotations = metaData.annotations(); + String serviceInfoAnnotation = String.format("%s:%s", "openapi", "ServiceInfo"); + return annotations.stream() + .filter(ann -> serviceInfoAnnotation.equals(ann.annotReference().toString().trim())) + .findFirst(); + } + + private Optional retrieveValueFromAnnotation(AnnotationNode annotation, String fieldName) { + return annotation + .annotValue() + .map(MappingConstructorExpressionNode::fields) + .flatMap(fields -> + fields.stream() + .filter(fld -> fld instanceof SpecificFieldNode) + .map(fld -> (SpecificFieldNode) fld) + .filter(fld -> fieldName.equals(fld.fieldName().toString().trim())) + .findFirst() + ).flatMap(SpecificFieldNode::valueExpr) + .map(en -> en.toString().trim()); + } + + protected Optional generateOpenApi(String fileName, Project project, SemanticModel semanticModel, + ServiceNode serviceNode) { + OASGenerationMetaInfo.OASGenerationMetaInfoBuilder builder = new + OASGenerationMetaInfo.OASGenerationMetaInfoBuilder(); + builder.setServiceNode(serviceNode) + .setSemanticModel(semanticModel) + .setOpenApiFileName(fileName) + .setBallerinaFilePath(null) + .setProject(project); + OASResult oasResult = ServiceToOpenAPIMapper.generateOAS(builder.build()); + Optional openApiOpt = oasResult.getOpenAPI(); + if (oasResult.getDiagnostics().stream().anyMatch( + diagnostic -> diagnostic.getDiagnosticSeverity().equals(DiagnosticSeverity.ERROR)) + || openApiOpt.isEmpty()) { + // Add warning diagnostic + return Optional.empty(); + } + OpenAPI openApi = openApiOpt.get(); + if (openApi.getInfo().getTitle() == null || openApi.getInfo().getTitle().equals("/")) { + openApi.getInfo().setTitle(normalizeTitle(fileName)); + } + return Optional.of(Json.pretty(openApi)); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java index bc548e22e7..0aac4114c2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/HttpServiceModifier.java @@ -22,7 +22,7 @@ import io.ballerina.projects.DocumentId; import io.ballerina.projects.plugins.CodeModifier; import io.ballerina.projects.plugins.CodeModifierContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.OpenApiInfoUpdaterTask; +import io.ballerina.stdlib.http.compiler.codemodifier.contract.ContractInfoModifierTask; import io.ballerina.stdlib.http.compiler.codemodifier.payload.HttpPayloadParamIdentifier; import io.ballerina.stdlib.http.compiler.codemodifier.payload.PayloadAnnotationModifierTask; import io.ballerina.stdlib.http.compiler.codemodifier.payload.context.PayloadParamContext; @@ -38,19 +38,21 @@ */ public class HttpServiceModifier extends CodeModifier { private final Map payloadParamContextMap; + private final Map ctxData; - public HttpServiceModifier() { + public HttpServiceModifier(Map ctxData) { this.payloadParamContextMap = new HashMap<>(); + this.ctxData = ctxData; } @Override public void init(CodeModifierContext codeModifierContext) { + ctxData.put("HTTP_CODE_MODIFIER_EXECUTED", true); codeModifierContext.addSyntaxNodeAnalysisTask( new HttpPayloadParamIdentifier(this.payloadParamContextMap), List.of(SyntaxKind.SERVICE_DECLARATION, SyntaxKind.CLASS_DEFINITION, SyntaxKind.OBJECT_TYPE_DESC)); codeModifierContext.addSourceModifierTask(new PayloadAnnotationModifierTask(this.payloadParamContextMap)); - codeModifierContext.addSourceModifierTask(new OpenApiInfoUpdaterTask()); - codeModifierContext.addSourceModifierTask(new ServiceTypeModifierTask()); + codeModifierContext.addSourceModifierTask(new ContractInfoModifierTask()); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java similarity index 72% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java index c3411523f8..04acbb807e 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/ServiceTypeModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java @@ -15,17 +15,19 @@ * specific language governing permissions and limitations * under the License. */ -package io.ballerina.stdlib.http.compiler.codemodifier; +package io.ballerina.stdlib.http.compiler.codemodifier.contract; import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; import io.ballerina.compiler.syntax.tree.AnnotationNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingFieldNode; import io.ballerina.compiler.syntax.tree.MetadataNode; import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; import io.ballerina.compiler.syntax.tree.ModulePartNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.SeparatedNodeList; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SpecificFieldNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; @@ -44,6 +46,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createIdentifierToken; import static io.ballerina.compiler.syntax.tree.AbstractNodeFactory.createNodeList; @@ -66,7 +69,7 @@ * * @since 2.12.0 */ -public class ServiceTypeModifierTask implements ModifierTask { +public class ContractInfoModifierTask implements ModifierTask { @Override public void modify(SourceModifierContext modifierContext) { @@ -147,13 +150,61 @@ private ServiceDeclarationNode updateServiceDeclarationNode(ServiceDeclarationNo String[] annotStrings = annotName.split(COLON); if (SERVICE_CONFIG_ANNOTATION.equals(annotStrings[annotStrings.length - 1].trim()) && HTTP.equals(annotStrings[0].trim())) { - return serviceDeclarationNode; + return updateServiceConfigAnnotation(serviceTypeDesc.get(), serviceDeclarationNode, annotation); } } return addServiceConfigAnnotation(serviceTypeDesc.get(), serviceDeclarationNode); } + private ServiceDeclarationNode updateServiceConfigAnnotation(TypeDescriptorNode serviceTypeDesc, + ServiceDeclarationNode serviceDeclarationNode, + AnnotationNode serviceConfigAnnotation) { + SpecificFieldNode serviceTypeField = createSpecificFieldNode(null, createIdentifierToken("serviceType"), + createToken(COLON_TOKEN), serviceTypeDesc); + Optional serviceConfigConstruct = serviceConfigAnnotation.annotValue(); + MappingConstructorExpressionNode newServiceConfigConstruct; + if (serviceConfigConstruct.isEmpty() || serviceConfigConstruct.get().fields().isEmpty()) { + newServiceConfigConstruct = createMappingConstructorExpressionNode( + createToken(SyntaxKind.OPEN_BRACE_TOKEN), createSeparatedNodeList(serviceTypeField), + createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); + } else { + MappingConstructorExpressionNode existingServiceConfigConstruct = serviceConfigConstruct.get(); + SeparatedNodeList fields = existingServiceConfigConstruct.fields(); + boolean hasServiceType = fields.stream().anyMatch(field -> { + if (field.kind().equals(SyntaxKind.SPECIFIC_FIELD)) { + SpecificFieldNode specificField = (SpecificFieldNode) field; + return specificField.fieldName().toString().equals("serviceType"); + } + return false; + }); + if (hasServiceType) { + return serviceDeclarationNode; + } + List fieldList = fields.stream().collect(Collectors.toList()); + fieldList.add(createToken(SyntaxKind.COMMA_TOKEN)); + fieldList.add(serviceTypeField); + SeparatedNodeList updatedFields = createSeparatedNodeList(fieldList); + newServiceConfigConstruct = createMappingConstructorExpressionNode( + createToken(SyntaxKind.OPEN_BRACE_TOKEN), updatedFields, + createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); + } + AnnotationNode newServiceConfigAnnotation = serviceConfigAnnotation.modify() + .withAnnotValue(newServiceConfigConstruct).apply(); + Optional metadata = serviceDeclarationNode.metadata(); + if (metadata.isEmpty()) { + MetadataNode metadataNode = createMetadataNode(null, + createNodeList(newServiceConfigAnnotation)); + return serviceDeclarationNode.modify().withMetadata(metadataNode).apply(); + } + + NodeList updatedAnnotations = metadata.get().annotations() + .remove(serviceConfigAnnotation) + .add(newServiceConfigAnnotation); + MetadataNode metadataNode = metadata.get().modify().withAnnotations(updatedAnnotations).apply(); + return serviceDeclarationNode.modify().withMetadata(metadataNode).apply(); + } + private ServiceDeclarationNode addServiceConfigAnnotation(TypeDescriptorNode serviceTypeDesc, ServiceDeclarationNode serviceDeclarationNode) { SpecificFieldNode serviceTypeField = createSpecificFieldNode(null, createIdentifierToken("serviceType"), diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java deleted file mode 100644 index 9c860b84d0..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/Constants.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas; - -/** - * {@code Constants} contains the common constants. - */ -public interface Constants { - // package details related constants - String PACKAGE_ORG = "ballerina"; - String PACKAGE_NAME = "openapi"; - - // open-api module related constants - String SERVICE_INFO_ANNOTATION_IDENTIFIER = "ServiceInfo"; - String CONTRACT = "contract"; - String EMBED = "embed"; - - // http module related constants - String HTTP_PACKAGE_NAME = "http"; - String SERVICE_CONFIG = "ServiceConfig"; - String SERVICE_CONTRACT_INFO = "ServiceContractInfo"; - String OPEN_API_DEFINITION_FIELD = "openApiDefinition"; - - String SLASH = "/"; -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java deleted file mode 100644 index 85c2ecb543..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/DocGenerationUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas; - -import io.ballerina.stdlib.http.compiler.HttpDiagnostic; -import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticFactory; -import io.ballerina.tools.diagnostics.DiagnosticInfo; -import io.ballerina.tools.diagnostics.Location; - -/** - * {@code DocGenerationUtils} contains common utilities related to doc generation and resource packaging. - */ -public final class DocGenerationUtils { - public static Diagnostic getDiagnostics(HttpDiagnostic errorCode, - Location location, Object... args) { - DiagnosticInfo diagnosticInfo = new DiagnosticInfo( - errorCode.getCode(), errorCode.getMessage(), errorCode.getSeverity()); - return DiagnosticFactory.createDiagnostic(diagnosticInfo, location, args); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java deleted file mode 100644 index 2cfe6776cf..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/HttpServiceAnalysisTask.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Project; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.DocGeneratorManager; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.gen.OpenApiDocConfig; - -/** - * {@code HttpServiceAnalysisTask} analyses the HTTP service for which the OpenApi doc is generated. - */ -public class HttpServiceAnalysisTask { - private final DocGeneratorManager docGenerator; - - public HttpServiceAnalysisTask() { - this.docGenerator = new DocGeneratorManager(); - } - - public void perform(ServiceNodeAnalysisContext context) { - Project currentProject = context.currentPackage().project(); - ServiceNode serviceNode = context.node(); - SemanticModel semanticModel = context.semanticModel(); - SyntaxTree syntaxTree = context.syntaxTree(); - OpenApiDocConfig docConfig = new OpenApiDocConfig(context.currentPackage(), - semanticModel, syntaxTree, serviceNode, currentProject.kind()); - this.docGenerator.generate(docConfig, context, serviceNode.location()); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java deleted file mode 100644 index 9ff96b3279..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/OpenApiInfoUpdaterTask.java +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.AbstractNodeFactory; -import io.ballerina.compiler.syntax.tree.AnnotationNode; -import io.ballerina.compiler.syntax.tree.ExpressionNode; -import io.ballerina.compiler.syntax.tree.IdentifierToken; -import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; -import io.ballerina.compiler.syntax.tree.MappingFieldNode; -import io.ballerina.compiler.syntax.tree.MetadataNode; -import io.ballerina.compiler.syntax.tree.ModuleMemberDeclarationNode; -import io.ballerina.compiler.syntax.tree.ModulePartNode; -import io.ballerina.compiler.syntax.tree.Node; -import io.ballerina.compiler.syntax.tree.NodeFactory; -import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.compiler.syntax.tree.NodeParser; -import io.ballerina.compiler.syntax.tree.QualifiedNameReferenceNode; -import io.ballerina.compiler.syntax.tree.SeparatedNodeList; -import io.ballerina.compiler.syntax.tree.SimpleNameReferenceNode; -import io.ballerina.compiler.syntax.tree.SpecificFieldNode; -import io.ballerina.compiler.syntax.tree.SyntaxKind; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.compiler.syntax.tree.Token; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Document; -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.Module; -import io.ballerina.projects.ModuleId; -import io.ballerina.projects.Package; -import io.ballerina.projects.plugins.ModifierTask; -import io.ballerina.projects.plugins.SourceModifierContext; -import io.ballerina.stdlib.http.compiler.HttpDiagnostic; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.DiagnosticFactory; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import io.ballerina.tools.text.TextDocument; - -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Base64; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -import static io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper.getServiceNode; -import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getDiagnosticInfo; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants.SERVICE_CONFIG; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants.SERVICE_CONTRACT_INFO; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContextHandler.getContextHandler; - -/** - * {@code OpenApiInfoUpdaterTask} modifies the source by including generated open-api spec for http-service - * declarations. - */ -public class OpenApiInfoUpdaterTask implements ModifierTask { - @Override - public void modify(SourceModifierContext context) { - boolean erroneousCompilation = context.compilation().diagnosticResult() - .diagnostics().stream() - .anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity())); - // if the compilation already contains any error, do not proceed - if (erroneousCompilation) { - return; - } - - // Mocking the code analysis task - mockServiceAnalyzerExecution(context); - - for (OpenApiDocContext openApiContext: getContextHandler().retrieveAvailableContexts()) { - ModuleId moduleId = openApiContext.getModuleId(); - Module currentModule = context.currentPackage().module(moduleId); - DocumentId documentId = openApiContext.getDocumentId(); - Document currentDoc = currentModule.document(documentId); - SemanticModel semanticModel = context.compilation().getSemanticModel(moduleId); - ModulePartNode rootNode = currentDoc.syntaxTree().rootNode(); - NodeList newMembers = updateMemberNodes( - rootNode.members(), openApiContext.getOpenApiDetails(), context, semanticModel); - ModulePartNode newModulePart = rootNode.modify(rootNode.imports(), newMembers, rootNode.eofToken()); - SyntaxTree updatedSyntaxTree = currentDoc.syntaxTree().modifyWith(newModulePart); - TextDocument textDocument = updatedSyntaxTree.textDocument(); - if (currentModule.documentIds().contains(documentId)) { - context.modifySourceFile(textDocument, documentId); - } else { - context.modifyTestSourceFile(textDocument, documentId); - } - } - } - - private static void mockServiceAnalyzerExecution(SourceModifierContext context) { - Package currentPackage = context.currentPackage(); - HttpServiceAnalysisTask serviceAnalysisTask = new HttpServiceAnalysisTask(); - for (ModuleId moduleId : currentPackage.moduleIds()) { - Module currentModule = currentPackage.module(moduleId); - SemanticModel semanticModel = context.compilation().getSemanticModel(moduleId); - for (DocumentId documentId : currentModule.documentIds()) { - Document currentDoc = currentModule.document(documentId); - SyntaxTree syntaxTree = currentDoc.syntaxTree(); - ModulePartNode rootNode = syntaxTree.rootNode(); - NodeList members = rootNode.members(); - for (ModuleMemberDeclarationNode member: members) { - Optional serviceNode = getServiceNode(member, semanticModel); - if (serviceNode.isEmpty()) { - continue; - } - ServiceNodeAnalysisContext serviceNodeAnalysisContext = new ServiceNodeAnalysisContext( - currentPackage, moduleId, documentId, syntaxTree, semanticModel, - serviceNode.get()); - serviceAnalysisTask.perform(serviceNodeAnalysisContext); - serviceNodeAnalysisContext.diagnostics().forEach(context::reportDiagnostic); - } - } - } - } - - private NodeList updateMemberNodes(NodeList oldMembers, - List openApi, - SourceModifierContext context, - SemanticModel semanticModel) { - List updatedMembers = new LinkedList<>(); - for (ModuleMemberDeclarationNode memberNode : oldMembers) { - Optional serviceNode = getServiceNode(memberNode, semanticModel); - if (serviceNode.isEmpty()) { - updatedMembers.add(memberNode); - continue; - } - updateServiceDeclarationNode(openApi, context, serviceNode.get(), updatedMembers); - } - return AbstractNodeFactory.createNodeList(updatedMembers); - } - - private void updateServiceDeclarationNode(List openApi, - SourceModifierContext context, ServiceNode serviceNode, - List updatedMembers) { - ModuleMemberDeclarationNode memberNode = serviceNode.getInternalNode(); - Optional openApiDefOpt = openApi.stream() - .filter(service -> service.getServiceId() == serviceNode.getServiceId()) - .findFirst(); - if (openApiDefOpt.isEmpty()) { - updatedMembers.add(memberNode); - return; - } - OpenApiDocContext.OpenApiDefinition openApiDef = openApiDefOpt.get(); - if (!openApiDef.isAutoEmbedToService()) { - updatedMembers.add(memberNode); - return; - } - NodeList existingAnnotations = serviceNode.metadata().map(MetadataNode::annotations) - .orElseGet(NodeFactory::createEmptyNodeList); - NodeList updatedAnnotations = updateAnnotations(existingAnnotations, - openApiDef.getDefinition(), context, serviceNode.kind().equals(ServiceNode.Kind.SERVICE_OBJECT_TYPE)); - serviceNode.updateAnnotations(updatedAnnotations); - updatedMembers.add(serviceNode.getInternalNode()); - } - - private NodeList updateAnnotations(NodeList currentAnnotations, - String openApiDef, SourceModifierContext context, - boolean isServiceContract) { - NodeList updatedAnnotations = NodeFactory.createNodeList(); - String annotationToBeUpdated = isServiceContract ? SERVICE_CONTRACT_INFO : SERVICE_CONFIG; - boolean annotationAlreadyExists = false; - for (AnnotationNode annotation: currentAnnotations) { - if (isHttpAnnotation(annotation, annotationToBeUpdated)) { - annotationAlreadyExists = true; - SeparatedNodeList updatedFields = getUpdatedFields(annotation, openApiDef, context); - MappingConstructorExpressionNode annotationValue = - NodeFactory.createMappingConstructorExpressionNode( - NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN), updatedFields, - NodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN)); - annotation = annotation.modify().withAnnotValue(annotationValue).apply(); - } - updatedAnnotations = updatedAnnotations.add(annotation); - } - if (!annotationAlreadyExists) { - AnnotationNode openApiAnnotation = getHttpAnnotationWithOpenApi(annotationToBeUpdated, openApiDef); - updatedAnnotations = updatedAnnotations.add(openApiAnnotation); - } - return updatedAnnotations; - } - - private SeparatedNodeList getUpdatedFields(AnnotationNode annotation, String servicePath, - SourceModifierContext context) { - Optional annotationValueOpt = annotation.annotValue(); - if (annotationValueOpt.isEmpty()) { - return NodeFactory.createSeparatedNodeList(createOpenApiDefinitionField(servicePath, false)); - } - List fields = new ArrayList<>(); - MappingConstructorExpressionNode annotationValue = annotationValueOpt.get(); - SeparatedNodeList existingFields = annotationValue.fields(); - Token separator = NodeFactory.createToken(SyntaxKind.COMMA_TOKEN); - MappingFieldNode openApiDefNode = null; - for (MappingFieldNode field : existingFields) { - if (field instanceof SpecificFieldNode) { - String fieldName = ((SpecificFieldNode) field).fieldName().toString(); - if (Constants.OPEN_API_DEFINITION_FIELD.equals(fieldName.trim())) { - openApiDefNode = field; - continue; - } - } - fields.add(field); - fields.add(separator); - } - fields.add(createOpenApiDefinitionField(servicePath, false)); - if (Objects.nonNull(openApiDefNode)) { - context.reportDiagnostic(DiagnosticFactory.createDiagnostic( - getDiagnosticInfo(HttpDiagnostic.HTTP_WARNING_102), openApiDefNode.location())); - } - return NodeFactory.createSeparatedNodeList(fields); - } - - private AnnotationNode getHttpAnnotationWithOpenApi(String annotationName, String openApiDefinition) { - String configIdentifierString = Constants.HTTP_PACKAGE_NAME + SyntaxKind.COLON_TOKEN.stringValue() + - annotationName; - IdentifierToken identifierToken = NodeFactory.createIdentifierToken(configIdentifierString); - Token atToken = NodeFactory.createToken(SyntaxKind.AT_TOKEN); - SimpleNameReferenceNode nameReferenceNode = NodeFactory.createSimpleNameReferenceNode(identifierToken); - MappingConstructorExpressionNode annotValue = getAnnotationExpression(openApiDefinition, - annotationName.equals(SERVICE_CONTRACT_INFO)); - return NodeFactory.createAnnotationNode(atToken, nameReferenceNode, annotValue); - } - - private MappingConstructorExpressionNode getAnnotationExpression(String openApiDefinition, - boolean isServiceContract) { - Token openBraceToken = NodeFactory.createToken(SyntaxKind.OPEN_BRACE_TOKEN); - Token closeBraceToken = NodeFactory.createToken(SyntaxKind.CLOSE_BRACE_TOKEN); - SpecificFieldNode specificFieldNode = createOpenApiDefinitionField(openApiDefinition, isServiceContract); - SeparatedNodeList separatedNodeList = NodeFactory.createSeparatedNodeList(specificFieldNode); - return NodeFactory.createMappingConstructorExpressionNode(openBraceToken, separatedNodeList, closeBraceToken); - } - - private static SpecificFieldNode createOpenApiDefinitionField(String openApiDefinition, - boolean isServiceContract) { - IdentifierToken fieldName = AbstractNodeFactory.createIdentifierToken(Constants.OPEN_API_DEFINITION_FIELD); - Token colonToken = AbstractNodeFactory.createToken(SyntaxKind.COLON_TOKEN); - String encodedValue = Base64.getEncoder().encodeToString(openApiDefinition.getBytes(Charset.defaultCharset())); - String format = isServiceContract ? "\"%s\"" : "base64 `%s`.cloneReadOnly()"; - ExpressionNode expressionNode = NodeParser.parseExpression(String.format(format, encodedValue)); - return NodeFactory.createSpecificFieldNode(null, fieldName, colonToken, expressionNode); - } - - private boolean isHttpAnnotation(AnnotationNode annotationNode, String annotationName) { - if (!(annotationNode.annotReference() instanceof QualifiedNameReferenceNode referenceNode)) { - return false; - } - if (!Constants.HTTP_PACKAGE_NAME.equals(referenceNode.modulePrefix().text())) { - return false; - } - return annotationName.equals(referenceNode.identifier().text()); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java deleted file mode 100644 index fb6b908107..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContext.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.context; - -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.ModuleId; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -/** - * {@code OpenApiDocContext} contains details related to open-api doc generation. - */ -public class OpenApiDocContext { - private final ModuleId moduleId; - private final DocumentId documentId; - private final List definitions = new ArrayList<>(); - - OpenApiDocContext(ModuleId moduleId, DocumentId documentId) { - this.moduleId = moduleId; - this.documentId = documentId; - } - - public ModuleId getModuleId() { - return moduleId; - } - - public DocumentId getDocumentId() { - return documentId; - } - - public List getOpenApiDetails() { - return Collections.unmodifiableList(definitions); - } - - void updateOpenApiDetails(OpenApiDefinition definition) { - this.definitions.add(definition); - } - - /** - * {@code OpenApiDefinition} contains details related to generated open-api definition. - */ - public static class OpenApiDefinition { - private final int serviceId; - private final String definition; - private final boolean embed; - - public OpenApiDefinition(int serviceId, String definition, boolean embed) { - this.serviceId = serviceId; - this.definition = definition; - this.embed = embed; - } - - public int getServiceId() { - return serviceId; - } - - public String getDefinition() { - return definition; - } - - public boolean isAutoEmbedToService() { - return embed; - } - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java deleted file mode 100644 index 52dca294f4..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/OpenApiDocContextHandler.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.context; - -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.ModuleId; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; - -/** - * {@code OpenApiDocContextHandler} will manage the shared context among compiler plugin tasks. - */ -public final class OpenApiDocContextHandler { - private static OpenApiDocContextHandler instance; - - private final List contexts; - - private OpenApiDocContextHandler(List contexts) { - this.contexts = contexts; - } - - public static OpenApiDocContextHandler getContextHandler() { - synchronized (OpenApiDocContextHandler.class) { - if (Objects.isNull(instance)) { - instance = new OpenApiDocContextHandler(new ArrayList<>()); - } - } - return instance; - } - - private void addContext(OpenApiDocContext context) { - synchronized (this.contexts) { - this.contexts.add(context); - } - } - - /** - * Update the shared context for open-api doc generation. - * @param moduleId of the current module - * @param documentId of the current file - * @param definition to be added to the context - */ - public void updateContext(ModuleId moduleId, DocumentId documentId, - OpenApiDocContext.OpenApiDefinition definition) { - Optional contextOpt = retrieveContext(moduleId, documentId); - if (contextOpt.isPresent()) { - OpenApiDocContext context = contextOpt.get(); - synchronized (context) { - context.updateOpenApiDetails(definition); - } - return; - } - OpenApiDocContext context = new OpenApiDocContext(moduleId, documentId); - context.updateOpenApiDetails(definition); - addContext(context); - } - - private Optional retrieveContext(ModuleId moduleId, DocumentId documentId) { - return this.contexts.stream() - .filter(ctx -> equals(ctx, moduleId, documentId)) - .findFirst(); - } - - public List retrieveAvailableContexts() { - return Collections.unmodifiableList(contexts); - } - - private boolean equals(OpenApiDocContext context, ModuleId moduleId, DocumentId documentId) { - int hashCodeForCurrentContext = Objects.hash(context.getModuleId(), context.getDocumentId()); - return hashCodeForCurrentContext == Objects.hash(moduleId, documentId); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java deleted file mode 100644 index 3b3a02cb4d..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/context/ServiceNodeAnalysisContext.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.context; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.DocumentId; -import io.ballerina.projects.ModuleId; -import io.ballerina.projects.Package; -import io.ballerina.tools.diagnostics.Diagnostic; - -import java.util.ArrayList; -import java.util.List; - -/** - * {@code ServiceNodeAnalysisContext} will store service node analysis context. - */ -public class ServiceNodeAnalysisContext { - - private final ServiceNode node; - private final ModuleId moduleId; - private final DocumentId documentId; - private final SyntaxTree syntaxTree; - private final SemanticModel semanticModel; - private final Package currentPackage; - private final List diagnostics; - - public ServiceNodeAnalysisContext(Package currentPackage, ModuleId moduleId, DocumentId documentId, - SyntaxTree syntaxTree, SemanticModel semanticModel, - ServiceNode node) { - this.moduleId = moduleId; - this.documentId = documentId; - this.syntaxTree = syntaxTree; - this.semanticModel = semanticModel; - this.currentPackage = currentPackage; - this.diagnostics = new ArrayList<>(); - this.node = node; - } - - public ServiceNode node() { - return node; - } - - public ModuleId moduleId() { - return moduleId; - } - - public DocumentId documentId() { - return documentId; - } - - public SyntaxTree syntaxTree() { - return syntaxTree; - } - - public SemanticModel semanticModel() { - return semanticModel; - } - - public Package currentPackage() { - return currentPackage; - } - - public void reportDiagnostic(Diagnostic diagnosticCode) { - diagnostics.add(diagnosticCode); - } - - public List diagnostics() { - return diagnostics; - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java deleted file mode 100644 index 016ad4f7cd..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/AbstractOpenApiDocGenerator.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.gen; - -import io.ballerina.compiler.syntax.tree.AnnotationNode; -import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; -import io.ballerina.compiler.syntax.tree.MetadataNode; -import io.ballerina.compiler.syntax.tree.NodeList; -import io.ballerina.compiler.syntax.tree.SpecificFieldNode; -import io.ballerina.openapi.service.mapper.ServiceToOpenAPIMapper; -import io.ballerina.openapi.service.mapper.model.OASGenerationMetaInfo; -import io.ballerina.openapi.service.mapper.model.OASResult; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Package; -import io.ballerina.projects.Project; -import io.ballerina.stdlib.http.compiler.HttpDiagnostic; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContext; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Diagnostic; -import io.ballerina.tools.diagnostics.DiagnosticSeverity; -import io.ballerina.tools.diagnostics.Location; -import io.swagger.v3.core.util.Json; -import io.swagger.v3.oas.models.OpenAPI; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; - -import static io.ballerina.openapi.service.mapper.utils.MapperCommonUtils.normalizeTitle; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.context.OpenApiDocContextHandler.getContextHandler; -import static io.ballerina.stdlib.http.compiler.codemodifier.oas.DocGenerationUtils.getDiagnostics; - -/** - * {@code AbstractOpenApiDocGenerator} contains the basic utilities required for OpenAPI doc generation. - */ -public abstract class AbstractOpenApiDocGenerator implements OpenApiDocGenerator { - private static final String FILE_NAME_FORMAT = "%d.json"; - - private final OpenApiContractResolver contractResolver; - - public AbstractOpenApiDocGenerator() { - this.contractResolver = new OpenApiContractResolver(); - } - - @Override - public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location) { - try { - int serviceId = config.getServiceId(); - Package currentPackage = config.currentPackage(); - Path srcRoot = currentPackage.project().sourceRoot(); - - // find the project root path - Path projectRoot = retrieveProjectRoot(srcRoot); - - ServiceNode serviceNode = config.serviceNode(); - Optional serviceInfoAnnotationOpt = getServiceInfoAnnotation(serviceNode); - if (serviceInfoAnnotationOpt.isPresent()) { - AnnotationNode serviceInfoAnnotation = serviceInfoAnnotationOpt.get(); - - boolean embed = retrieveValueForAnnotationFields(serviceInfoAnnotation, Constants.EMBED) - .map(Boolean::parseBoolean) - .orElse(false); - - // use the available open-api doc and update the context - OpenApiContractResolver.ResolverResponse resolverResponse = this.contractResolver - .resolve(serviceInfoAnnotation, projectRoot); - if (resolverResponse.isContractAvailable()) { - // could not find the open-api contract file, hence will not proceed - if (resolverResponse.getContractPath().isEmpty()) { - return; - } - String openApiDefinition = Files.readString(resolverResponse.getContractPath().get()); - updateOpenApiContext(context, serviceId, openApiDefinition, embed); - } else { - // generate open-api doc and update the context if the `contract` configuration is not available - generateOpenApiDoc(currentPackage.project(), config, context, location, embed); - } - } else if (serviceNode.kind().equals(ServiceNode.Kind.SERVICE_OBJECT_TYPE)) { - // generate open-api doc and update the context if the service is a service contract type - generateOpenApiDoc(currentPackage.project(), config, context, location, true); - } - } catch (IOException | RuntimeException e) { - // currently, we do not have open-api doc generation logic for following scenarios: - // 1. default resources and for scenarios - // 2. returning http-response from a resource - // hence logs are disabled for now - } - } - - private void updateOpenApiContext(ServiceNodeAnalysisContext context, int serviceId, String openApiDefinition, - boolean embed) { - OpenApiDocContext.OpenApiDefinition openApiDef = new OpenApiDocContext.OpenApiDefinition(serviceId, - openApiDefinition, embed); - getContextHandler().updateContext(context.moduleId(), context.documentId(), openApiDef); - } - - private void updateCompilerContext(ServiceNodeAnalysisContext context, Location location, - HttpDiagnostic errorCode) { - Diagnostic diagnostic = getDiagnostics(errorCode, location); - context.reportDiagnostic(diagnostic); - } - - private Optional getServiceInfoAnnotation(ServiceNode serviceNode) { - Optional metadata = serviceNode.metadata(); - if (metadata.isEmpty()) { - return Optional.empty(); - } - MetadataNode metaData = metadata.get(); - NodeList annotations = metaData.annotations(); - String serviceInfoAnnotation = String.format("%s:%s", - Constants.PACKAGE_NAME, Constants.SERVICE_INFO_ANNOTATION_IDENTIFIER); - return annotations.stream() - .filter(ann -> serviceInfoAnnotation.equals(ann.annotReference().toString().trim())) - .findFirst(); - } - - private Optional retrieveValueForAnnotationFields(AnnotationNode serviceInfoAnnotation, String fieldName) { - return serviceInfoAnnotation - .annotValue() - .map(MappingConstructorExpressionNode::fields) - .flatMap(fields -> - fields.stream() - .filter(fld -> fld instanceof SpecificFieldNode) - .map(fld -> (SpecificFieldNode) fld) - .filter(fld -> fieldName.equals(fld.fieldName().toString().trim())) - .findFirst() - ).flatMap(SpecificFieldNode::valueExpr) - .map(en -> en.toString().trim()); - } - - private void generateOpenApiDoc(Project project, OpenApiDocConfig config, ServiceNodeAnalysisContext context, - Location location, boolean embed) { - if (!embed) { - return; - } - int serviceId = config.getServiceId(); - String targetFile = String.format(FILE_NAME_FORMAT, serviceId); - OASGenerationMetaInfo.OASGenerationMetaInfoBuilder builder = new - OASGenerationMetaInfo.OASGenerationMetaInfoBuilder(); - builder.setServiceNode(config.serviceNode()) - .setSemanticModel(config.semanticModel()) - .setOpenApiFileName(targetFile) - .setBallerinaFilePath(null) - .setProject(project); - OASResult oasResult = ServiceToOpenAPIMapper.generateOAS(builder.build()); - Optional openApiOpt = oasResult.getOpenAPI(); - if (oasResult.getDiagnostics().stream().anyMatch( - diagnostic -> diagnostic.getDiagnosticSeverity().equals(DiagnosticSeverity.ERROR)) - || openApiOpt.isEmpty()) { - HttpDiagnostic errorCode = HttpDiagnostic.HTTP_WARNING_101; - updateCompilerContext(context, location, errorCode); - return; - } - OpenAPI openApi = openApiOpt.get(); - if (openApi.getInfo().getTitle() == null || openApi.getInfo().getTitle().equals(Constants.SLASH)) { - openApi.getInfo().setTitle(normalizeTitle(targetFile)); - } - String openApiDefinition = Json.pretty(openApi); - updateOpenApiContext(context, serviceId, openApiDefinition, true); - } - - protected Path retrieveProjectRoot(Path projectRoot) { - return projectRoot; - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java deleted file mode 100644 index a31c71cdf1..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/BalProjectOpenApiDocGenerator.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.gen; - -import io.ballerina.projects.ProjectKind; - -/** - * {@code BalProjectOpenApiDocGenerator} generates open-api related docs for HTTP service defined in ballerina projects. - */ -public class BalProjectOpenApiDocGenerator extends AbstractOpenApiDocGenerator { - @Override - public boolean isSupported(ProjectKind projectType) { - return ProjectKind.BUILD_PROJECT.equals(projectType); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java deleted file mode 100644 index 43b385171c..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/DocGeneratorManager.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.gen; - -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Location; - -import java.util.List; - -/** - * {@code DocGeneratorManager} manages OpenAPI doc generation for HTTP services depending on whether the current project - * is a ballerina-project or a single ballerina file. - */ -public final class DocGeneratorManager { - private final List docGenerators; - - public DocGeneratorManager() { - this.docGenerators = List.of(new SingleFileOpenApiDocGenerator(), new BalProjectOpenApiDocGenerator()); - } - - public void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location) { - docGenerators.stream() - .filter(dg -> dg.isSupported(config.projectType())) - .findFirst() - .ifPresent(dg -> dg.generate(config, context, location)); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java deleted file mode 100644 index 164e4909ea..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiContractResolver.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.gen; - -import io.ballerina.compiler.syntax.tree.AnnotationNode; -import io.ballerina.compiler.syntax.tree.ExpressionNode; -import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; -import io.ballerina.compiler.syntax.tree.MappingFieldNode; -import io.ballerina.compiler.syntax.tree.SeparatedNodeList; -import io.ballerina.compiler.syntax.tree.SpecificFieldNode; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.Constants; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; - -/** - * {@code OpenApiContractResolver} resolves the provided OpenAPI doc via `openapi:ServiceInfo` annotation. - */ -public final class OpenApiContractResolver { - public ResolverResponse resolve(AnnotationNode serviceInfoAnnotation, Path projectRoot) { - Optional mappingConstructorExpressionNode = - serviceInfoAnnotation.annotValue(); - if (mappingConstructorExpressionNode.isEmpty()) { - // if details not available do not proceed - return new ResolverResponse(false); - } - - MappingConstructorExpressionNode exprNode = mappingConstructorExpressionNode.get(); - SeparatedNodeList fieldsOpt = exprNode.fields(); - if (fieldsOpt.isEmpty()) { - // if details not available do not proceed - return new ResolverResponse(false); - } - - Optional> annotationFieldsOpt = serviceInfoAnnotation - .annotValue().map(MappingConstructorExpressionNode::fields); - if (annotationFieldsOpt.isEmpty()) { - // annotation fields are not available, hence will not proceed - return new ResolverResponse(false); - } - - SeparatedNodeList annotationFields = annotationFieldsOpt.get(); - Optional contractFieldOpt = annotationFields.stream() - .filter(fld -> fld instanceof SpecificFieldNode) - .map(fld -> (SpecificFieldNode) fld) - .filter(fld -> Constants.CONTRACT.equals(fld.fieldName().toString().trim())) - .findFirst(); - if (contractFieldOpt.isEmpty()) { - // could not find the `contract` field in the service-info annotation, hence will not proceed - return new ResolverResponse(false); - } - - SpecificFieldNode openApiContract = contractFieldOpt.get(); - Optional openApiContractValueOpt = openApiContract.valueExpr(); - if (openApiContractValueOpt.isEmpty()) { - // could not find the value for `contract` field in the service-info annotation, - // hence will not proceed - return new ResolverResponse(true); - } - - ExpressionNode openApiContractValue = openApiContractValueOpt.get(); - String openApiContractPath = openApiContractValue.toString() - .replaceAll("\"", "").trim(); - if (openApiContractPath.isBlank()) { - // `contract` value is empty, hence will not proceed - return new ResolverResponse(true); - } - - Path pathToOpenApiContract = getPathToOpenApiContract(openApiContractPath, projectRoot); - if (!Files.exists(pathToOpenApiContract)) { - // could not find open-api contract file, hence will not proceed - return new ResolverResponse(true); - } - - return new ResolverResponse(pathToOpenApiContract); - } - - private Path getPathToOpenApiContract(String openApiPath, Path projectRoot) { - Path openApiDocPath = Paths.get(openApiPath); - if (openApiDocPath.isAbsolute()) { - return openApiDocPath; - } else { - return projectRoot.resolve(openApiDocPath); - } - } - - /** - * {@code ResolverResponse} contains the response for OpenAPI doc retrieval via `openapi:ServiceInfo` annotation. - */ - public static class ResolverResponse { - private final boolean contractAvailable; - private Path contractPath; - - ResolverResponse(boolean contractAvailable) { - this.contractAvailable = contractAvailable; - } - - ResolverResponse(Path contractPath) { - this.contractAvailable = true; - this.contractPath = contractPath; - } - - public boolean isContractAvailable() { - return contractAvailable; - } - - public Optional getContractPath() { - return Optional.ofNullable(this.contractPath); - } - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java deleted file mode 100644 index 7fa6ddf1a2..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.gen; - -import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.openapi.service.mapper.model.ServiceNode; -import io.ballerina.projects.Package; -import io.ballerina.projects.ProjectKind; - -/** - * {@code OpenApiDocConfig} contains the configurations related to generate OpenAPI doc. - * - * @param currentPackage current package - * @param semanticModel semantic model - * @param syntaxTree syntax tree - * @param serviceNode service node - * @param projectType project type - */ -public record OpenApiDocConfig(Package currentPackage, SemanticModel semanticModel, SyntaxTree syntaxTree, - ServiceNode serviceNode, ProjectKind projectType) { - - public int getServiceId() { - return serviceNode.getServiceId(); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java deleted file mode 100644 index 6313ac807d..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/OpenApiDocGenerator.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.gen; - -import io.ballerina.projects.ProjectKind; -import io.ballerina.stdlib.http.compiler.codemodifier.oas.context.ServiceNodeAnalysisContext; -import io.ballerina.tools.diagnostics.Location; - -/** - * {@code Generator} generates open-api related docs for HTTP service. - */ -public interface OpenApiDocGenerator { - void generate(OpenApiDocConfig config, ServiceNodeAnalysisContext context, Location location); - - boolean isSupported(ProjectKind projectType); -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java deleted file mode 100644 index a771d3c7c8..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/oas/gen/SingleFileOpenApiDocGenerator.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) All Rights Reserved. - * - * 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.codemodifier.oas.gen; - -import io.ballerina.projects.ProjectKind; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Objects; - -/** - * {@code SingleFileOpenApiDocGenerator} generates open-api related docs for HTTP service defined in single ballerina - * file. - */ -public class SingleFileOpenApiDocGenerator extends AbstractOpenApiDocGenerator { - @Override - public boolean isSupported(ProjectKind projectType) { - return ProjectKind.SINGLE_FILE_PROJECT.equals(projectType); - } - - @Override - protected Path retrieveProjectRoot(Path projectRoot) { - // For single ballerina file, project root will be the absolute path for that particular ballerina file - // hence project root should be updated to the directory which contains the ballerina file - Path parentDirectory = retrieveParentDirectory(projectRoot); - if (Objects.nonNull(parentDirectory) && Files.exists(parentDirectory)) { - return parentDirectory; - } - return projectRoot.isAbsolute() ? projectRoot : projectRoot.toAbsolutePath(); - } - - private Path retrieveParentDirectory(Path projectRoot) { - if (projectRoot.isAbsolute()) { - return projectRoot.getParent(); - } else { - return projectRoot.toAbsolutePath().getParent(); - } - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java index 21ed4d453d..6995a88aac 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/HttpPayloadParamIdentifier.java @@ -68,13 +68,15 @@ import static io.ballerina.stdlib.http.compiler.Constants.TABLE_OF_ANYDATA_MAP; import static io.ballerina.stdlib.http.compiler.Constants.TUPLE_OF_ANYDATA; import static io.ballerina.stdlib.http.compiler.Constants.XML; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.diagnosticContainsErrors; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getCtxTypes; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getServiceDeclarationNode; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.subtypeOf; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.updateDiagnostic; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.getEffectiveType; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.isValidBasicParamType; import static io.ballerina.stdlib.http.compiler.HttpResourceValidator.isValidNilableBasicParamType; -import static io.ballerina.stdlib.http.compiler.HttpServiceObjTypeAnalyzer.isHttpServiceType; /** diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java index 24d4befcc7..d22d33faf2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/payload/PayloadAnnotationModifierTask.java @@ -60,7 +60,7 @@ import java.util.List; import java.util.Map; -import static io.ballerina.stdlib.http.compiler.HttpServiceObjTypeAnalyzer.isHttpServiceType; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; import static io.ballerina.stdlib.http.compiler.HttpServiceValidator.isServiceContractImplementation; /** diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index 5270c8105f..ccdf7bcd84 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -44,11 +44,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; @@ -238,6 +241,7 @@ public static HttpService buildHttpService(BObject service, String basePath) { HttpService httpService = new HttpService(service, basePath); BMap serviceConfig = getHttpServiceConfigAnnotation(service); httpService.populateServiceConfig(serviceConfig); + httpService.populateIntrospectionPayload(); return httpService; } @@ -248,6 +252,7 @@ protected void populateServiceConfig(BMap serviceConfig) { this.setChunkingConfig(serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_CHUNKING).toString()); this.setCorsHeaders(CorsHeaders.buildCorsHeaders(serviceConfig.getMapValue(CORS_FIELD))); this.setHostName(serviceConfig.getStringValue(HOST_FIELD).getValue().trim()); + // TODO: Remove once the field is removed from the annotation this.setIntrospectionPayload(serviceConfig.getArrayValue(OPENAPI_DEF_FIELD).getByteArray()); if (serviceConfig.containsKey(MEDIA_TYPE_SUBTYPE_PREFIX)) { this.setMediaTypeSubtypePrefix(serviceConfig.getStringValue(MEDIA_TYPE_SUBTYPE_PREFIX) @@ -413,6 +418,36 @@ protected static BMap getServiceConfigAnnotation(BObject service, String package fromString(key + ":" + annotationName)); } + private static Optional getOpenApiDocFileName(BObject service) { + BMap openApiDocMap = (BMap) ((ObjectType) TypeUtils.getReferredType(TypeUtils.getType(service))).getAnnotation( + fromString("ballerina/lang.annotations:0:IntrospectionDocConfig")); + if (Objects.isNull(openApiDocMap)) { + return Optional.empty(); + } + BString name = openApiDocMap.getStringValue(fromString("name")); + return Objects.isNull(name) ? Optional.empty() : Optional.of(name.getValue()); + } + + protected void populateIntrospectionPayload() { + Optional openApiFileNameOpt = getOpenApiDocFileName(balService); + if (openApiFileNameOpt.isEmpty()) { + return; + } + // Load from resources + String openApiFileName = openApiFileNameOpt.get(); + String openApiDocPath = String.format("resources/openapi_%s.json", + openApiFileName.startsWith("-") ? "0" + openApiFileName.substring(1) : openApiFileName); + try (InputStream is = HttpService.class.getClassLoader().getResourceAsStream(openApiDocPath)) { + if (Objects.isNull(is)) { + log.debug("OpenAPI definition is not available in the resources"); + return; + } + this.setIntrospectionPayload(is.readAllBytes()); + } catch (IOException e) { + log.debug("Error while loading OpenAPI definition from resources", e); + } + } + @Override public String getOasResourceLink() { if (this.oasResourceLinks.isEmpty()) { 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 e687cf069f..531521ac73 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 @@ -48,6 +48,7 @@ protected HttpServiceFromContract(BObject service, String basePath, ReferenceTyp public static HttpService buildHttpService(BObject service, String basePath, ReferenceType serviceContractType) { HttpService httpService = new HttpServiceFromContract(service, basePath, serviceContractType); + httpService.populateIntrospectionPayload(); BMap serviceConfig = getHttpServiceConfigAnnotation(serviceContractType); httpService.populateServiceConfig(serviceConfig); return httpService; From b78bc22ce3e8ffe30360dd4c0629023ade5b4999 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Mon, 15 Jul 2024 10:12:54 +0530 Subject: [PATCH 28/47] Update jwt version --- ballerina-tests/http-advanced-tests/Dependencies.toml | 2 +- ballerina-tests/http-client-tests/Dependencies.toml | 2 +- ballerina-tests/http-dispatching-tests/Dependencies.toml | 2 +- ballerina-tests/http-interceptor-tests/Dependencies.toml | 2 +- ballerina-tests/http-misc-tests/Dependencies.toml | 2 +- ballerina-tests/http-resiliency-tests/Dependencies.toml | 2 +- ballerina-tests/http-security-tests/Dependencies.toml | 2 +- ballerina-tests/http-service-tests/Dependencies.toml | 2 +- ballerina-tests/http2-tests/Dependencies.toml | 2 +- ballerina/Dependencies.toml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index 0675047570..8f1b189428 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -160,7 +160,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index 2d92732bb6..af922821bc 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index c46c3c2f07..ab233f390a 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -159,7 +159,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index 752e593483..6822c1981a 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -147,7 +147,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index 02586a163c..e3a60823a9 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -156,7 +156,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index 1169612507..d6732f594e 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -154,7 +154,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index cc2dc1ee1d..9fc2acd6e2 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -152,7 +152,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index 0524b04fdb..eac3d335bf 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index a6c1478a05..737d289c6b 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -156,7 +156,7 @@ scope = "testOnly" [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" scope = "testOnly" dependencies = [ {org = "ballerina", name = "cache"}, diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index e1b07647c4..876cf01ecf 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -128,7 +128,7 @@ modules = [ [[package]] org = "ballerina" name = "jwt" -version = "2.12.1" +version = "2.11.0" dependencies = [ {org = "ballerina", name = "cache"}, {org = "ballerina", name = "crypto"}, From 54115e02bd22316579cca2530acbcf6e7c72e89c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 17 Jul 2024 16:11:56 +0530 Subject: [PATCH 29/47] Move service contract type logic to start method --- .../stdlib/http/api/HTTPServicesRegistry.java | 25 +++++++++++++++++++ .../http/api/service/endpoint/Start.java | 8 ++++-- 2 files changed, 31 insertions(+), 2 deletions(-) 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 419ed95e24..22e36aaeb0 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 @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; @@ -40,6 +41,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import static io.ballerina.stdlib.http.api.HttpConstants.DEFAULT_BASE_PATH; import static io.ballerina.stdlib.http.api.HttpConstants.DEFAULT_HOST; import static io.ballerina.stdlib.http.api.HttpUtil.checkConfigAnnotationAvailability; @@ -59,6 +61,7 @@ public class HTTPServicesRegistry { protected List sortedServiceURIs; private Runtime runtime; private boolean possibleLastService = true; + private List serviceContractImpls = new ArrayList<>(); /** * Get ServiceInfo instance for given interface and base path. @@ -108,10 +111,28 @@ public List getSortedServiceURIsByHost(String hostName) { */ public void registerService(BObject service, String basePathFromDeclaration) { Optional serviceContractType = getServiceContractType(service); + if (serviceContractType.isPresent()) { + serviceContractImpls.add(service); + return; + } HttpService httpService = serviceContractType.map(referenceType -> HttpServiceFromContract.buildHttpService(service, basePathFromDeclaration, referenceType)).orElseGet( () -> HttpService.buildHttpService(service, basePathFromDeclaration)); + registerBallerinaService(service, httpService); + } + + public void registerServiceImplementedByContract(BObject service) { + Optional serviceContractType = getServiceContractType(service); + if (serviceContractType.isEmpty()) { + return; + } + HttpService httpService = HttpServiceFromContract.buildHttpService(service, DEFAULT_BASE_PATH, + serviceContractType.get()); + registerBallerinaService(service, httpService); + } + + private void registerBallerinaService(BObject service, HttpService httpService) { String basePath = httpService.getBasePath(); service.addNativeData(HttpConstants.ABSOLUTE_RESOURCE_PATH, basePath); String hostName = httpService.getHostName(); @@ -141,6 +162,10 @@ public void registerService(BObject service, String basePathFromDeclaration) { sortedServiceURIs.sort((basePath1, basePath2) -> basePath2.length() - basePath1.length()); } + public List getServiceContractImpls() { + return serviceContractImpls; + } + private static Optional getServiceContractType(BObject service) { BMap serviceConfig = HttpService.getHttpServiceConfigAnnotation(service); diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/service/endpoint/Start.java b/native/src/main/java/io/ballerina/stdlib/http/api/service/endpoint/Start.java index 7a93a52a46..d4766ebc34 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/service/endpoint/Start.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/service/endpoint/Start.java @@ -48,10 +48,14 @@ public static Object start(Environment env, BObject listener) { } private static Object startServerConnector(Environment env, BObject serviceEndpoint) { - // TODO : Move this to `register` after this issue is fixed + // TODO : Move these to `register` after this issue is fixed // https://github.com/ballerina-platform/ballerina-lang/issues/33594 - // Get and populate interceptor services HTTPServicesRegistry httpServicesRegistry = getHttpServicesRegistry(serviceEndpoint); + // Register services implemented via service contract type + for (BObject serviceContractImpl : httpServicesRegistry.getServiceContractImpls()) { + httpServicesRegistry.registerServiceImplementedByContract(serviceContractImpl); + } + // Get and populate interceptor services Runtime runtime = getRuntime(env, httpServicesRegistry); try { HttpUtil.populateInterceptorServicesFromListener(serviceEndpoint, runtime); From dbe80841d5f78d983ee87b579f5be09dbed3b3da Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 17 Jul 2024 16:12:16 +0530 Subject: [PATCH 30/47] Remove parameter annotation restriction --- .../stdlib/http/compiler/CompilerPluginTest.java | 10 ++++------ .../http/compiler/CompilerPluginTestConstants.java | 1 - .../stdlib/http/compiler/HttpResourceValidator.java | 7 ------- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java index fc8dc36ff0..a7cbe1bd5e 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java @@ -146,7 +146,7 @@ public void testInValidInputPayloadArgs() { Package currentPackage = loadPackage("sample_package_4"); PackageCompilation compilation = currentPackage.getCompilation(); DiagnosticResult diagnosticResult = compilation.diagnosticResult(); - Assert.assertEquals(diagnosticResult.errorCount(), 8); + Assert.assertEquals(diagnosticResult.errorCount(), 7); assertError(diagnosticResult, 0, "invalid multiple resource parameter annotations for 'abc': expected one of " + "the following types: 'http:Payload', 'http:CallerInfo', 'http:Header', 'http:Query'", HTTP_108); assertError(diagnosticResult, 1, "invalid usage of payload annotation for a non entity body " + @@ -155,13 +155,11 @@ public void testInValidInputPayloadArgs() { "resource : 'head'. Use an accessor that supports entity body", HTTP_129); assertError(diagnosticResult, 3, "invalid usage of payload annotation for a non entity body resource" + " : 'options'. Use an accessor that supports entity body", HTTP_129); - assertError(diagnosticResult, 4, "invalid annotation type on param 'a': expected one of the following types: " + - "'http:Payload', 'http:CallerInfo', 'http:Header', 'http:Query'", CompilerPluginTestConstants.HTTP_104); - assertTrue(diagnosticResult, 5, "invalid payload parameter type: 'string|ballerina/http:", + assertTrue(diagnosticResult, 4, "invalid payload parameter type: 'string|ballerina/http:", CompilerPluginTestConstants.HTTP_107); - assertTrue(diagnosticResult, 6, "invalid payload parameter type:", + assertTrue(diagnosticResult, 5, "invalid payload parameter type:", CompilerPluginTestConstants.HTTP_107); - assertTrue(diagnosticResult, 7, "invalid payload parameter type:", + assertTrue(diagnosticResult, 6, "invalid payload parameter type:", CompilerPluginTestConstants.HTTP_107); } diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java index a48c65c936..68004d801e 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTestConstants.java @@ -26,7 +26,6 @@ private CompilerPluginTestConstants() {} public static final String HTTP_101 = "HTTP_101"; public static final String HTTP_102 = "HTTP_102"; - public static final String HTTP_104 = "HTTP_104"; public static final String HTTP_105 = "HTTP_105"; public static final String HTTP_106 = "HTTP_106"; public static final String HTTP_107 = "HTTP_107"; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java index 9cb7d7e4ec..d2fac3e9a8 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpResourceValidator.java @@ -392,7 +392,6 @@ public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ct continue; } if (!HTTP.equals(nameSymbolOptional.get())) { - reportInvalidParameterAnnotation(ctx, paramLocation, paramName); continue; } @@ -465,7 +464,6 @@ public static void extractInputParamTypeAndValidate(SyntaxNodeAnalysisContext ct break; } default: - reportInvalidParameterAnnotation(ctx, paramLocation, paramName); break; } } @@ -934,11 +932,6 @@ private static boolean isValidReturnTypeWithCaller(TypeSymbol returnTypeDescript } } - private static void reportInvalidParameterAnnotation(SyntaxNodeAnalysisContext ctx, Location location, - String paramName) { - updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_104, paramName); - } - private static void reportInvalidParameter(SyntaxNodeAnalysisContext ctx, Location location, String paramName) { updateDiagnostic(ctx, location, HttpDiagnostic.HTTP_105, paramName); From fd5b57fc767e299bcbaa7fd9a11decc0209f7375 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 25 Jul 2024 09:33:39 +0530 Subject: [PATCH 31/47] Update introspection resource only if it is empty --- .../main/java/io/ballerina/stdlib/http/api/HttpService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java index ccdf7bcd84..20d29c39e6 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpService.java @@ -222,7 +222,9 @@ public void setCorsHeaders(CorsHeaders corsHeaders) { } public void setIntrospectionPayload(byte[] introspectionPayload) { - this.introspectionPayload = introspectionPayload.clone(); + if (this.introspectionPayload.length == 0) { + this.introspectionPayload = introspectionPayload.clone(); + } } public byte[] getIntrospectionPayload() { From fa6644c7ca28a4148cfc10d81a84e35e04f04432 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 25 Jul 2024 09:58:48 +0530 Subject: [PATCH 32/47] Update dependency versions --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index d053a153ec..b35e444199 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.stdlib version=2.12.0-SNAPSHOT -ballerinaLangVersion=2201.10.0-20240711-141800-6245661f +ballerinaLangVersion=2201.10.0-20240724-195400-9de60831 ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 nettyVersion=4.1.108.Final @@ -23,7 +23,7 @@ lz4Version=1.3.0 marshallingVersion=2.0.5.Final protobufVersion=3.20.3 jacocoVersion=0.8.10 -ballerinaToOpenApiVersion=2.1.0-20240715-091523-93f4d1b +ballerinaToOpenApiVersion=2.1.0-20240725-093624-8c3f00a swaggerVersion=2.2.9 stdlibIoVersion=1.6.0 From 865d42c6c0a54d8af471b61d4a62c5ed4e28cedd Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 26 Jul 2024 10:34:36 +0530 Subject: [PATCH 33/47] Update dependency versions --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index b35e444199..92c479c289 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.stdlib version=2.12.0-SNAPSHOT -ballerinaLangVersion=2201.10.0-20240724-195400-9de60831 +ballerinaLangVersion=2201.10.0-20240726-135400-47fe7f7d ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 nettyVersion=4.1.108.Final @@ -23,7 +23,7 @@ lz4Version=1.3.0 marshallingVersion=2.0.5.Final protobufVersion=3.20.3 jacocoVersion=0.8.10 -ballerinaToOpenApiVersion=2.1.0-20240725-093624-8c3f00a +ballerinaToOpenApiVersion=2.1.0-20240726-174559-e9190c3 swaggerVersion=2.2.9 stdlibIoVersion=1.6.0 From d08e9221716af1cddf6b2f78de6112a09f0d44a5 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 10:25:16 +0530 Subject: [PATCH 34/47] Add ballerina test case --- .../tests/service_contract_tests.bal | 116 ++++++++++++ .../tests/test_service_ports.bal | 2 + ballerina-tests/http-test-common/types.bal | 177 ++++++++++++++++++ 3 files changed, 295 insertions(+) create mode 100644 ballerina-tests/http-advanced-tests/tests/service_contract_tests.bal create mode 100644 ballerina-tests/http-test-common/types.bal diff --git a/ballerina-tests/http-advanced-tests/tests/service_contract_tests.bal b/ballerina-tests/http-advanced-tests/tests/service_contract_tests.bal new file mode 100644 index 0000000000..81a48e24f0 --- /dev/null +++ b/ballerina-tests/http-advanced-tests/tests/service_contract_tests.bal @@ -0,0 +1,116 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). +// +// 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. + +import ballerina/crypto; +import ballerina/http; +import ballerina/http_test_common as common; +import ballerina/test; + +final http:Client serviceContractClient = check new (string `localhost:${serviceContractTestPort}/socialMedia`); + +service common:Service on new http:Listener(serviceContractTestPort) { + + resource function get users() returns common:User[]|error { + return [{id: 1, name: "Alice", email: "alice@gmail.com"}, {id: 2, name: "Bob", email: "bob@gmail.com"}]; + } + + resource function get users/[int id]() returns common:User|common:UserNotFound|error { + return {id: 1, name: "Alice", email: "alice@gmail.com"}; + } + + resource function post users(common:NewUser newUser) returns http:Created|error { + return { + body: { + message: "User created successfully" + } + }; + } + + resource function delete users/[int id]() returns http:NoContent|error { + return http:NO_CONTENT; + } + + resource function get users/[int id]/posts() returns common:PostWithMeta[]|common:UserNotFound|error { + return [{id: 1, content: "Content 1", createdAt: "2020-01-01T10:00:00Z"}, {id: 2, content: "Content 2", createdAt: "2020-01-01T10:00:00Z"}]; + } + + resource function post users/[int id]/posts(common:NewPost newPost) returns http:Created|common:UserNotFound|common:PostForbidden|error { + return error("invalid post"); + } + + public function createInterceptors() returns common:ErrorInterceptor { + return new (); + } +} + +@test:Config {} +function testCachingWithServiceContract() returns error? { + http:Response response = check serviceContractClient->/users; + json payload = [{id: 1, name: "Alice", email: "alice@gmail.com"}, {id: 2, name: "Bob", email: "bob@gmail.com"}]; + test:assertTrue(response.hasHeader(common:LAST_MODIFIED)); + common:assertHeaderValue(check response.getHeader(common:CACHE_CONTROL), "must-revalidate,public,max-age=10"); + common:assertHeaderValue(check response.getHeader(common:ETAG), crypto:crc32b(payload.toString().toBytes())); + common:assertHeaderValue(check response.getHeader(common:CONTENT_TYPE), "application/vnd.socialMedia+json"); + common:assertJsonPayload(response.getJsonPayload(), payload); +} + +@test:Config {} +function testLinksInServiceContract() returns error? { + record{*http:Links; *common:User;} response = check serviceContractClient->/users/'2; + map expectedLinks = { + "delete-user": { + href: "/socialMedia/users/{id}", + types: ["application/vnd.socialMedia+json"], + methods: [http:DELETE] + }, + "create-posts": { + href: "/socialMedia/users/{id}/posts", + types: ["text/vnd.socialMedia+plain"], + methods: [http:POST] + }, + "get-posts": { + href: "/socialMedia/users/{id}/posts", + types: ["application/vnd.socialMedia+json", "text/vnd.socialMedia+plain"], + methods: [http:GET] + } + }; + record{} payload = {"id": 1, "name": "Alice", "email": "alice@gmail.com", "_links": expectedLinks}; + test:assertEquals(response, payload); +} + +@test:Config {} +function testPayloadAnnotationWithServiceContract() returns error? { + common:NewUser newUser = {name: "Alice", email: "alice@gmail.com"}; + http:Response response = check serviceContractClient->/users.post(newUser); + test:assertEquals(response.statusCode, 415); + common:assertTextPayload(response.getTextPayload(), "content-type : application/json is not supported"); + + record{string message;} result = check serviceContractClient->/users.post(newUser, mediaType = "application/vnd.socialMedia+json"); + test:assertEquals(result.message, "User created successfully"); +} + +@test:Config {} +function testInterceptorWithServiceContract() returns error? { + common:NewPost newPost = {content: "sample content"}; + http:Response response = check serviceContractClient->/users/'1/posts.post(newPost, mediaType = "application/vnd.socialMedia+json"); + test:assertEquals(response.statusCode, 500); + common:assertJsonPayload(response.getTextPayload(), "invalid post"); + + json invalidPost = {message: "sample content"}; + response = check serviceContractClient->/users/'1/posts.post(invalidPost, mediaType = "application/vnd.socialMedia+json"); + test:assertEquals(response.statusCode, 400); + common:assertTrueTextPayload(response.getTextPayload(), "data binding failed"); +} diff --git a/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal b/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal index 1d6a9cda66..e6b955084c 100644 --- a/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal +++ b/ballerina-tests/http-advanced-tests/tests/test_service_ports.bal @@ -48,3 +48,5 @@ const int cookieTestPort2 = 9254; const int http1SsePort = 9094; const int http2SsePort = 9095; + +const int serviceContractTestPort = 9096; diff --git a/ballerina-tests/http-test-common/types.bal b/ballerina-tests/http-test-common/types.bal new file mode 100644 index 0000000000..571f681f1d --- /dev/null +++ b/ballerina-tests/http-test-common/types.bal @@ -0,0 +1,177 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). +// +// 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. + +import ballerina/http; +import ballerina/log; + +# The service type that handles the social media API +@http:ServiceConfig { + mediaTypeSubtypePrefix: "vnd.socialMedia", + basePath: "/socialMedia" +} +public type Service service object { + *http:ServiceContract; + *http:InterceptableService; + + public function createInterceptors() returns ErrorInterceptor; + + # Get all users + # + # + return - List of users(`User[]`) or an error + @http:ResourceConfig { + name: "users" + } + resource function get users() returns @http:Cache {maxAge: 10} User[]|error; + + # Get a user by ID + # + # + id - User ID + # + return - `User` or NotFound response(`UserNotFound`) when the user is not found or an error + @http:ResourceConfig { + name: "user", + linkedTo: [ + {name: "user", method: http:DELETE, relation: "delete-user"}, + {name: "posts", method: http:POST, relation: "create-posts"}, + {name: "posts", method: http:GET, relation: "get-posts"} + ] + } + resource function get users/[int id]() returns User|UserNotFound|error; + + # Create a new user + # + # + newUser - New user details(`NewUser`) as payload + # + return - Created(`http:Created`) response or an error + @http:ResourceConfig { + name: "users", + linkedTo: [ + {name: "user", method: http:GET, relation: "get-user"}, + {name: "user", method: http:DELETE, relation: "delete-user"}, + {name: "posts", method: http:POST, relation: "create-posts"}, + {name: "posts", method: http:GET, relation: "get-posts"} + ], + consumes: ["application/vnd.socialMedia+json"] + } + resource function post users(NewUser newUser) returns http:Created|error; + + # Delete a user by ID + # + # + id - User ID + # + return - NoContent response(`http:NoContent`) or an error + @http:ResourceConfig { + name: "user" + } + resource function delete users/[int id]() returns http:NoContent|error; + + # Get all posts of a user + # + # + id - User ID + # + return - List of posts with metadata(`PostWithMeta[]`) or NotFound response(`UserNotFound`) when the user is not found or an error + @http:ResourceConfig { + name: "posts" + } + resource function get users/[int id]/posts() returns @http:Cache {maxAge: 25} PostWithMeta[]|UserNotFound|error; + + # Create a new post for a user + # + # + id - User ID + # + newPost - New post details(`NewPost`) as payload + # + return - Created(`http:Created`) response or an error + @http:ResourceConfig { + name: "posts", + linkedTo: [ + {name: "posts", method: http:POST, relation: "create-posts"} + ], + consumes: ["application/vnd.socialMedia+json"] + } + resource function post users/[int id]/posts(@http:Payload NewPost newPost) returns http:Created|UserNotFound|PostForbidden|error; +}; + +public isolated service class ErrorInterceptor { + *http:ResponseErrorInterceptor; + + isolated remote function interceptResponseError(error err, http:Response res, http:RequestContext ctx) returns DefaultResponse { + log:printError("error occurred", err); + return { + body: err.message(), + status: new (res.statusCode) + }; + } +} + +# Represents a user in the system +# +# + id - user ID +# + name - user name +# + email - user email +public type User record { + int id; + string name; + string email; +}; + +# Represents a new user +# +# + name - user name +# + email - user email +public type NewUser record { + string name; + string email; +}; + +# Represents a user not found error +# +# + body - error message +public type UserNotFound record {| + *http:NotFound; + ErrorMessage body; +|}; + +# Represents a new post +# +# + content - post content +public type NewPost record { + string content; +}; + +# Represents a post with metadata +# +# + id - post ID +# + content - post content +# + createdAt - post creation time +public type PostWithMeta record { + int id; + string content; + string createdAt; +}; + +# Represents a post forbidden error +# +# + body - error message +public type PostForbidden record {| + *http:Forbidden; + ErrorMessage body; +|}; + +# Represents a default response +# +# + body - response body +public type DefaultResponse record {| + *http:DefaultStatusCodeResponse; + ErrorMessage body; +|}; + +# Represents a error message +public type ErrorMessage string; From 2e9c16f71e3633e804f6271009e97e47bd5922c0 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 13:18:10 +0530 Subject: [PATCH 35/47] Update dependency versions --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index bad98839ca..d4ab0649b5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ org.gradle.caching=true group=io.ballerina.stdlib version=2.12.0-SNAPSHOT -ballerinaLangVersion=2201.10.0-20240726-135400-47fe7f7d +ballerinaLangVersion=2201.10.0-20240801-104200-87df251c ballerinaTomlParserVersion=1.2.2 commonsLang3Version=3.12.0 nettyVersion=4.1.108.Final @@ -23,7 +23,7 @@ lz4Version=1.3.0 marshallingVersion=2.0.5.Final protobufVersion=3.20.3 jacocoVersion=0.8.10 -ballerinaToOpenApiVersion=2.1.0-20240726-174559-e9190c3 +ballerinaToOpenApiVersion=2.1.0-20240801-125916-f3852a9 swaggerVersion=2.2.9 stdlibIoVersion=1.6.0 From ef2dab83dcca4d1916d586d10b2bdbdee7a35405 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 13:54:10 +0530 Subject: [PATCH 36/47] Fix service contract annotation for test resources --- .../codemodifier/contract/ContractInfoModifierTask.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java index 04acbb807e..70a8d1106a 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codemodifier/contract/ContractInfoModifierTask.java @@ -96,6 +96,9 @@ private void modifyServiceDeclarationsPerModule(SourceModifierContext modifierCo for (DocumentId documentId : currentModule.documentIds()) { modifyServiceDeclarationsPerDocument(modifierContext, documentId, currentModule); } + for (DocumentId documentId : currentModule.testDocumentIds()) { + modifyServiceDeclarationsPerDocument(modifierContext, documentId, currentModule); + } } private void modifyServiceDeclarationsPerDocument(SourceModifierContext modifierContext, DocumentId documentId, From 524a117b9fad1f2165a699b09e3ff75d76fc80f0 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 14:05:57 +0530 Subject: [PATCH 37/47] Add code action test --- compiler-plugin-tests/spotbugs-exclude.xml | 4 + .../ImplementServiceContractTest.java | 71 ++++++ .../Ballerina.toml | 4 + .../sample_codeaction_package_4/service.bal | 186 ++++++++++++++++ .../result1.bal | 210 ++++++++++++++++++ 5 files changed, 475 insertions(+) create mode 100644 compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContractTest.java create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/service.bal create mode 100644 compiler-plugin-tests/src/test/resources/codeaction/implement_service_contract_resources/result1.bal diff --git a/compiler-plugin-tests/spotbugs-exclude.xml b/compiler-plugin-tests/spotbugs-exclude.xml index 6f10e573e5..7ac289191f 100644 --- a/compiler-plugin-tests/spotbugs-exclude.xml +++ b/compiler-plugin-tests/spotbugs-exclude.xml @@ -36,6 +36,10 @@ + + + + diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContractTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContractTest.java new file mode 100644 index 0000000000..3d178c41aa --- /dev/null +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/codeaction/ImplementServiceContractTest.java @@ -0,0 +1,71 @@ +/* + * 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.projects.plugins.codeaction.CodeActionArgument; +import io.ballerina.projects.plugins.codeaction.CodeActionInfo; +import io.ballerina.tools.text.LinePosition; +import io.ballerina.tools.text.LineRange; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.List; + +import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; + +/** + * Tests the implement service contract resources code action when a service is implemented via + * the service contract type. + */ +public class ImplementServiceContractTest extends AbstractCodeActionTest { + @Test(dataProvider = "testDataProvider") + public void testCodeActions(String srcFile, int line, int offset, CodeActionInfo expected, String resultFile) + throws IOException { + Path filePath = RESOURCE_PATH.resolve("ballerina_sources") + .resolve("sample_codeaction_package_4") + .resolve(srcFile); + Path resultPath = RESOURCE_PATH.resolve("codeaction") + .resolve(getConfigDir()) + .resolve(resultFile); + + performTest(filePath, LinePosition.from(line, offset), expected, resultPath); + } + + @DataProvider + private Object[][] testDataProvider() { + return new Object[][]{ + {"service.bal", 183, 12, getExpectedCodeAction(), "result1.bal"} + }; + } + + private CodeActionInfo getExpectedCodeAction() { + LineRange lineRange = LineRange.from("service.bal", LinePosition.from(183, 0), + LinePosition.from(185, 1)); + CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, lineRange); + CodeActionInfo codeAction = CodeActionInfo.from("Implement service contract resources", + List.of(locationArg)); + codeAction.setProviderName("HTTP_HINT_105/ballerina/http/IMPLEMENT_SERVICE_CONTRACT"); + return codeAction; + } + + protected String getConfigDir() { + return "implement_service_contract_resources"; + } +} diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/Ballerina.toml b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/Ballerina.toml new file mode 100644 index 0000000000..0fba9fca75 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "http_test" +name = "codeaction_sample_4" +version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/service.bal b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/service.bal new file mode 100644 index 0000000000..a7c0cea0bc --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_codeaction_package_4/service.bal @@ -0,0 +1,186 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). +// +// 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. + +// This is added to test some auto generated code segments. +// Please ignore the indentation. + +import ballerina/http; + +import ballerina/http; +import ballerina/log; + +# The service type that handles the social media API +@http:ServiceConfig { + mediaTypeSubtypePrefix: "vnd.socialMedia", + basePath: "/socialMedia" +} +public type Service service object { + *http:ServiceContract; + *http:InterceptableService; + + public function createInterceptors() returns ErrorInterceptor; + + # Get all users + # + # + return - List of users(`User[]`) or an error + @http:ResourceConfig { + name: "users" + } + resource function get users() returns @http:Cache {maxAge: 10} User[]|error; + + # Get a user by ID + # + # + id - User ID + # + return - `User` or NotFound response(`UserNotFound`) when the user is not found or an error + @http:ResourceConfig { + name: "user", + linkedTo: [ + {name: "user", method: http:DELETE, relation: "delete-user"}, + {name: "posts", method: http:POST, relation: "create-posts"}, + {name: "posts", method: http:GET, relation: "get-posts"} + ] + } + resource function get users/[int id]() returns User|UserNotFound|error; + + # Create a new user + # + # + newUser - New user details(`NewUser`) as payload + # + return - Created(`http:Created`) response or an error + @http:ResourceConfig { + name: "users", + linkedTo: [ + {name: "user", method: http:GET, relation: "get-user"}, + {name: "user", method: http:DELETE, relation: "delete-user"}, + {name: "posts", method: http:POST, relation: "create-posts"}, + {name: "posts", method: http:GET, relation: "get-posts"} + ], + consumes: ["application/vnd.socialMedia+json"] + } + resource function post users(NewUser newUser) returns http:Created|error; + + # Delete a user by ID + # + # + id - User ID + # + return - NoContent response(`http:NoContent`) or an error + @http:ResourceConfig { + name: "user" + } + resource function delete users/[int id]() returns http:NoContent|error; + + # Get all posts of a user + # + # + id - User ID + # + return - List of posts with metadata(`PostWithMeta[]`) or NotFound response(`UserNotFound`) when the user is not found or an error + @http:ResourceConfig { + name: "posts" + } + resource function get users/[int id]/posts() returns @http:Cache {maxAge: 25} PostWithMeta[]|UserNotFound|error; + + # Create a new post for a user + # + # + id - User ID + # + newPost - New post details(`NewPost`) as payload + # + return - Created(`http:Created`) response or an error + @http:ResourceConfig { + name: "posts", + linkedTo: [ + {name: "posts", method: http:POST, relation: "create-posts"} + ], + consumes: ["application/vnd.socialMedia+json"] + } + resource function post users/[int id]/posts(@http:Payload NewPost newPost) returns http:Created|UserNotFound|PostForbidden|error; +}; + +public isolated service class ErrorInterceptor { + *http:ResponseErrorInterceptor; + + isolated remote function interceptResponseError(error err, http:Response res, http:RequestContext ctx) returns DefaultResponse { + log:printError("error occurred", err); + return { + body: err.message(), + status: new (res.statusCode) + }; + } +} + +# Represents a user in the system +# +# + id - user ID +# + name - user name +# + email - user email +public type User record { + int id; + string name; + string email; +}; + +# Represents a new user +# +# + name - user name +# + email - user email +public type NewUser record { + string name; + string email; +}; + +# Represents a user not found error +# +# + body - error message +public type UserNotFound record {| + *http:NotFound; + ErrorMessage body; +|}; + +# Represents a new post +# +# + content - post content +public type NewPost record { + string content; +}; + +# Represents a post with metadata +# +# + id - post ID +# + content - post content +# + createdAt - post creation time +public type PostWithMeta record { + int id; + string content; + string createdAt; +}; + +# Represents a post forbidden error +# +# + body - error message +public type PostForbidden record {| + *http:Forbidden; + ErrorMessage body; +|}; + +# Represents a default response +# +# + body - response body +public type DefaultResponse record {| + *http:DefaultStatusCodeResponse; + ErrorMessage body; +|}; + +# Represents a error message +public type ErrorMessage string; + +service Service on new http:Listener(9090) { + +} diff --git a/compiler-plugin-tests/src/test/resources/codeaction/implement_service_contract_resources/result1.bal b/compiler-plugin-tests/src/test/resources/codeaction/implement_service_contract_resources/result1.bal new file mode 100644 index 0000000000..e132b5262f --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/codeaction/implement_service_contract_resources/result1.bal @@ -0,0 +1,210 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.org). +// +// 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. + +// This is added to test some auto generated code segments. +// Please ignore the indentation. + +import ballerina/http; + +import ballerina/http; +import ballerina/log; + +# The service type that handles the social media API +@http:ServiceConfig { + mediaTypeSubtypePrefix: "vnd.socialMedia", + basePath: "/socialMedia" +} +public type Service service object { + *http:ServiceContract; + *http:InterceptableService; + + public function createInterceptors() returns ErrorInterceptor; + + # Get all users + # + # + return - List of users(`User[]`) or an error + @http:ResourceConfig { + name: "users" + } + resource function get users() returns @http:Cache {maxAge: 10} User[]|error; + + # Get a user by ID + # + # + id - User ID + # + return - `User` or NotFound response(`UserNotFound`) when the user is not found or an error + @http:ResourceConfig { + name: "user", + linkedTo: [ + {name: "user", method: http:DELETE, relation: "delete-user"}, + {name: "posts", method: http:POST, relation: "create-posts"}, + {name: "posts", method: http:GET, relation: "get-posts"} + ] + } + resource function get users/[int id]() returns User|UserNotFound|error; + + # Create a new user + # + # + newUser - New user details(`NewUser`) as payload + # + return - Created(`http:Created`) response or an error + @http:ResourceConfig { + name: "users", + linkedTo: [ + {name: "user", method: http:GET, relation: "get-user"}, + {name: "user", method: http:DELETE, relation: "delete-user"}, + {name: "posts", method: http:POST, relation: "create-posts"}, + {name: "posts", method: http:GET, relation: "get-posts"} + ], + consumes: ["application/vnd.socialMedia+json"] + } + resource function post users(NewUser newUser) returns http:Created|error; + + # Delete a user by ID + # + # + id - User ID + # + return - NoContent response(`http:NoContent`) or an error + @http:ResourceConfig { + name: "user" + } + resource function delete users/[int id]() returns http:NoContent|error; + + # Get all posts of a user + # + # + id - User ID + # + return - List of posts with metadata(`PostWithMeta[]`) or NotFound response(`UserNotFound`) when the user is not found or an error + @http:ResourceConfig { + name: "posts" + } + resource function get users/[int id]/posts() returns @http:Cache {maxAge: 25} PostWithMeta[]|UserNotFound|error; + + # Create a new post for a user + # + # + id - User ID + # + newPost - New post details(`NewPost`) as payload + # + return - Created(`http:Created`) response or an error + @http:ResourceConfig { + name: "posts", + linkedTo: [ + {name: "posts", method: http:POST, relation: "create-posts"} + ], + consumes: ["application/vnd.socialMedia+json"] + } + resource function post users/[int id]/posts(@http:Payload NewPost newPost) returns http:Created|UserNotFound|PostForbidden|error; +}; + +public isolated service class ErrorInterceptor { + *http:ResponseErrorInterceptor; + + isolated remote function interceptResponseError(error err, http:Response res, http:RequestContext ctx) returns DefaultResponse { + log:printError("error occurred", err); + return { + body: err.message(), + status: new (res.statusCode) + }; + } +} + +# Represents a user in the system +# +# + id - user ID +# + name - user name +# + email - user email +public type User record { + int id; + string name; + string email; +}; + +# Represents a new user +# +# + name - user name +# + email - user email +public type NewUser record { + string name; + string email; +}; + +# Represents a user not found error +# +# + body - error message +public type UserNotFound record {| + *http:NotFound; + ErrorMessage body; +|}; + +# Represents a new post +# +# + content - post content +public type NewPost record { + string content; +}; + +# Represents a post with metadata +# +# + id - post ID +# + content - post content +# + createdAt - post creation time +public type PostWithMeta record { + int id; + string content; + string createdAt; +}; + +# Represents a post forbidden error +# +# + body - error message +public type PostForbidden record {| + *http:Forbidden; + ErrorMessage body; +|}; + +# Represents a default response +# +# + body - response body +public type DefaultResponse record {| + *http:DefaultStatusCodeResponse; + ErrorMessage body; +|}; + +# Represents a error message +public type ErrorMessage string; + +service Service on new http:Listener(9090) { + + + resource function get users () returns User[]|error { + + } + + resource function get users/[int id] () returns User|UserNotFound|error { + + } + + resource function post users (NewUser newUser) returns http:Created|error { + + } + + resource function delete users/[int id] () returns http:NoContent|error { + + } + + resource function get users/[int id]/posts () returns PostWithMeta[]|UserNotFound|error { + + } + + resource function post users/[int id]/posts (NewPost newPost) returns http:Created|UserNotFound|PostForbidden|error { + + } +} From 9b45f1d8284bed5e1d1e563f7dfcaafb2d52de79 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 14:06:18 +0530 Subject: [PATCH 38/47] [Automated] Update the native jar versions --- ballerina/CompilerPlugin.toml | 3 +++ ballerina/Dependencies.toml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ballerina/CompilerPlugin.toml b/ballerina/CompilerPlugin.toml index fc0dcc7e77..bd4522d9b1 100644 --- a/ballerina/CompilerPlugin.toml +++ b/ballerina/CompilerPlugin.toml @@ -4,3 +4,6 @@ class = "io.ballerina.stdlib.http.compiler.HttpCompilerPlugin" [[dependency]] path = "../compiler-plugin/build/libs/http-compiler-plugin-2.12.0-SNAPSHOT.jar" + +[[dependency]] +path = "../compiler-plugin/build/libs/ballerina-to-openapi-2.1.0-20240801-125916-f3852a9.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 773ff416a5..abc1a52610 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" From 2db3f470727ba93cebe74650f1c21360514dd14c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 14:17:44 +0530 Subject: [PATCH 39/47] [Automated] Update the native jar versions --- .../http-advanced-tests/Dependencies.toml | 4 +- .../http-client-tests/Dependencies.toml | 4 +- .../http-dispatching-tests/Dependencies.toml | 4 +- .../http-interceptor-tests/Dependencies.toml | 4 +- .../http-misc-tests/Dependencies.toml | 4 +- .../http-resiliency-tests/Dependencies.toml | 4 +- .../http-security-tests/Dependencies.toml | 4 +- .../http-service-tests/Dependencies.toml | 4 +- .../http-test-common/Dependencies.toml | 170 +++++++++++++++++- ballerina-tests/http2-tests/Dependencies.toml | 4 +- 10 files changed, 196 insertions(+), 10 deletions(-) diff --git a/ballerina-tests/http-advanced-tests/Dependencies.toml b/ballerina-tests/http-advanced-tests/Dependencies.toml index 04e390bc8c..874342ae83 100644 --- a/ballerina-tests/http-advanced-tests/Dependencies.toml +++ b/ballerina-tests/http-advanced-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -128,7 +128,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-client-tests/Dependencies.toml b/ballerina-tests/http-client-tests/Dependencies.toml index 54d7be4553..ecef451d88 100644 --- a/ballerina-tests/http-client-tests/Dependencies.toml +++ b/ballerina-tests/http-client-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -124,7 +124,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-dispatching-tests/Dependencies.toml b/ballerina-tests/http-dispatching-tests/Dependencies.toml index 1ef2a9ab2b..49a169a3ed 100644 --- a/ballerina-tests/http-dispatching-tests/Dependencies.toml +++ b/ballerina-tests/http-dispatching-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -127,7 +127,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-interceptor-tests/Dependencies.toml b/ballerina-tests/http-interceptor-tests/Dependencies.toml index ae942ef0fc..c724994724 100644 --- a/ballerina-tests/http-interceptor-tests/Dependencies.toml +++ b/ballerina-tests/http-interceptor-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -118,7 +118,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-misc-tests/Dependencies.toml b/ballerina-tests/http-misc-tests/Dependencies.toml index ce1f712703..efc7f1262b 100644 --- a/ballerina-tests/http-misc-tests/Dependencies.toml +++ b/ballerina-tests/http-misc-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -121,7 +121,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-resiliency-tests/Dependencies.toml b/ballerina-tests/http-resiliency-tests/Dependencies.toml index 966f56380f..ab3a54da27 100644 --- a/ballerina-tests/http-resiliency-tests/Dependencies.toml +++ b/ballerina-tests/http-resiliency-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -119,7 +119,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-security-tests/Dependencies.toml b/ballerina-tests/http-security-tests/Dependencies.toml index 3839f305ab..3da196359d 100644 --- a/ballerina-tests/http-security-tests/Dependencies.toml +++ b/ballerina-tests/http-security-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -123,7 +123,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-service-tests/Dependencies.toml b/ballerina-tests/http-service-tests/Dependencies.toml index c7a2fab1db..b6a071eda3 100644 --- a/ballerina-tests/http-service-tests/Dependencies.toml +++ b/ballerina-tests/http-service-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -124,7 +124,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, diff --git a/ballerina-tests/http-test-common/Dependencies.toml b/ballerina-tests/http-test-common/Dependencies.toml index 566d0bbfaa..f7db575b4d 100644 --- a/ballerina-tests/http-test-common/Dependencies.toml +++ b/ballerina-tests/http-test-common/Dependencies.toml @@ -5,14 +5,99 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.11.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.9.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} +] [[package]] org = "ballerina" name = "http_test_common" version = "2.12.0" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, @@ -36,6 +121,20 @@ org = "ballerina" name = "jballerina.java" version = "0.0.0" +[[package]] +org = "ballerina" +name = "jwt" +version = "2.12.1" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + [[package]] org = "ballerina" name = "lang.__internal" @@ -54,6 +153,14 @@ dependencies = [ {org = "ballerina", name = "lang.__internal"} ] +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "lang.error" @@ -85,6 +192,14 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "lang.string" @@ -105,6 +220,20 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "log" +version = "2.9.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerina", packageName = "log", moduleName = "log"} +] + [[package]] org = "ballerina" name = "mime" @@ -118,6 +247,45 @@ modules = [ {org = "ballerina", packageName = "mime", moduleName = "mime"} ] +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.11.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.2.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + [[package]] org = "ballerina" name = "test" diff --git a/ballerina-tests/http2-tests/Dependencies.toml b/ballerina-tests/http2-tests/Dependencies.toml index 47aae04eef..7692dbd048 100644 --- a/ballerina-tests/http2-tests/Dependencies.toml +++ b/ballerina-tests/http2-tests/Dependencies.toml @@ -5,7 +5,7 @@ [ballerina] dependencies-toml-version = "2" -distribution-version = "2201.10.0-20240711-141800-6245661f" +distribution-version = "2201.10.0-20240801-104200-87df251c" [[package]] org = "ballerina" @@ -124,7 +124,9 @@ name = "http_test_common" version = "2.12.0" scope = "testOnly" dependencies = [ + {org = "ballerina", name = "http"}, {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, {org = "ballerina", name = "mime"}, {org = "ballerina", name = "test"}, {org = "ballerina", name = "time"}, From 3f3d7ae54fe43510f1ac3cc76e0a262f09581f25 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 16:04:35 +0530 Subject: [PATCH 40/47] Change the diagnostic location --- .../io/ballerina/stdlib/http/compiler/HttpServiceValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 de39ea5810..2cacde8bbc 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 @@ -346,7 +346,7 @@ private static void validateAnnotationUsageForServiceContractType(SyntaxNodeAnal String actualServiceType = strings[1].trim(); if (!actualServiceType.equals(expectedServiceType)) { reportInvalidServiceContractType(ctx, expectedServiceType, actualServiceType, - annotation.location()); + field.location()); return; } } else if (!("openApiDefinition".equals(strings[0].trim()))) { From a9eece2447b070ea122983df80656dd977ce4ee1 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 16:17:17 +0530 Subject: [PATCH 41/47] Add a positive compiler plugin test --- .../http/compiler/CompilerPluginTest.java | 8 ++ .../sample_package_41/Ballerina.toml | 4 + .../sample_package_41/service.bal | 104 ++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/service.bal diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java index 48e4aee166..92e1aafe3e 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/CompilerPluginTest.java @@ -921,4 +921,12 @@ public void testServiceContractValidations() { " via the 'http:ServiceContract' type. The HTTP annotations are inferred from the service contract" + " type", HTTP_160); } + + @Test + public void testServiceContractSuccess() { + Package currentPackage = loadPackage("sample_package_41"); + PackageCompilation compilation = currentPackage.getCompilation(); + DiagnosticResult diagnosticResult = compilation.diagnosticResult(); + Assert.assertEquals(diagnosticResult.errorCount(), 0); + } } diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/Ballerina.toml b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/Ballerina.toml new file mode 100644 index 0000000000..78b28feb26 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "http_test" +name = "sample_41" +version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/service.bal b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/service.bal new file mode 100644 index 0000000000..ce73b5ac41 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/ballerina_sources/sample_package_41/service.bal @@ -0,0 +1,104 @@ +import ballerina/http; +import ballerina/log; + +@http:ServiceConfig { + mediaTypeSubtypePrefix: "vnd.socialMedia", + basePath: "/socialMedia" +} +public type Service service object { + *http:ServiceContract; + *http:InterceptableService; + + public function createInterceptors() returns ErrorInterceptor; + + resource function get users() returns @http:Cache {maxAge: 10} User[]|error; + + resource function get users/[int id]() returns User|UserNotFound|error; + + resource function post users(NewUser newUser) returns http:Created|error; +}; + +public isolated service class ErrorInterceptor { + *http:ResponseErrorInterceptor; + + isolated remote function interceptResponseError(error err, http:Response res, http:RequestContext ctx) returns DefaultResponse { + log:printError("error occurred", err); + return { + body: err.message(), + status: new (res.statusCode) + }; + } +} + +public type User record { + int id; + string name; + string email; +}; + +public type NewUser record { + string name; + string email; +}; + + +public type UserNotFound record {| + *http:NotFound; + ErrorMessage body; +|}; + +public type DefaultResponse record {| + *http:DefaultStatusCodeResponse; + ErrorMessage body; +|}; + +public type ErrorMessage string; + +service Service on new http:Listener(9090) { + + resource function get users() returns User[]|error { + return [{id: 1, name: "Alice", email: "alice@gmail.com"}, {id: 2, name: "Bob", email: "bob@gmail.com"}]; + } + + resource function get users/[int id]() returns User|UserNotFound|error { + return {id: 1, name: "Alice", email: "alice@gmail.com"}; + } + + resource function post users(NewUser newUser) returns http:Created|error { + return { + body: { + message: "User created successfully" + } + }; + } + + public function createInterceptors() returns ErrorInterceptor { + return new (); + } +} + +@http:ServiceConfig { + serviceType: Service +} +service Service on new http:Listener(9091) { + + resource function get users() returns User[]|error { + return [{id: 1, name: "Alice", email: "alice@gmail.com"}, {id: 2, name: "Bob", email: "bob@gmail.com"}]; + } + + resource function get users/[int id]() returns User|UserNotFound|error { + return {id: 1, name: "Alice", email: "alice@gmail.com"}; + } + + resource function post users(NewUser newUser) returns http:Created|error { + return { + body: { + message: "User created successfully" + } + }; + } + + public function createInterceptors() returns ErrorInterceptor { + return new (); + } +} From f5fdf91864ca4e5e17d9eb1d86f8b40f74bfca33 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 16:17:37 +0530 Subject: [PATCH 42/47] Restructure the oas related generators --- .../io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java | 2 ++ .../http/compiler/{ => oas}/ServiceContractOasGenerator.java | 2 +- .../stdlib/http/compiler/{ => oas}/ServiceOasGenerator.java | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/{ => oas}/ServiceContractOasGenerator.java (98%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/{ => oas}/ServiceOasGenerator.java (99%) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java index 83504cb8f5..a5c13ca0e9 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceAnalyzer.java @@ -21,6 +21,8 @@ import io.ballerina.compiler.syntax.tree.SyntaxKind; import io.ballerina.projects.plugins.CodeAnalysisContext; import io.ballerina.projects.plugins.CodeAnalyzer; +import io.ballerina.stdlib.http.compiler.oas.ServiceContractOasGenerator; +import io.ballerina.stdlib.http.compiler.oas.ServiceOasGenerator; import java.util.Map; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceContractOasGenerator.java similarity index 98% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceContractOasGenerator.java index a833d8228a..d5ce82ecf9 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceContractOasGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceContractOasGenerator.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations * under the License. */ -package io.ballerina.stdlib.http.compiler; +package io.ballerina.stdlib.http.compiler.oas; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java similarity index 99% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java index 701bf8e44a..ca5de330cc 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/ServiceOasGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java @@ -15,7 +15,7 @@ * specific language governing permissions and limitations * under the License. */ -package io.ballerina.stdlib.http.compiler; +package io.ballerina.stdlib.http.compiler.oas; import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.symbols.Symbol; From 8f09e272c135950a838363d434be26559b557e2f Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 1 Aug 2024 16:17:57 +0530 Subject: [PATCH 43/47] Skip oas generator folder for code-coverage --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index a24e1d32da..e23fd2e114 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,6 +5,7 @@ fixes: ignore: - "ballerina-tests" - "test-utils" + - "compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas" # This is covered in OpenAPI tool coverage: precision: 2 From 976acb53dbb1e2940cc4460f542def5134d033a9 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 2 Aug 2024 08:52:13 +0530 Subject: [PATCH 44/47] Update with generated resource directory API --- .../http/compiler/oas/ServiceOasGenerator.java | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java index ca5de330cc..553991f939 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java @@ -40,7 +40,6 @@ import java.io.FileWriter; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.util.Optional; @@ -108,17 +107,7 @@ protected static String getFileName(int hashCode) { } protected static void writeOpenApiAsTargetResource(Project project, String fileName, String openApi) { - Path targetPath = project.targetDir(); - // Create a folder resources if not exists - Path resourcesPath = targetPath.resolve("resources"); - if (!resourcesPath.toFile().exists()) { - try { - Files.createDirectory(resourcesPath); - } catch (IOException e) { - // Add warning diagnostic - return; - } - } + Path resourcesPath = project.generatedResourcesDir(); writeFile(fileName, openApi, resourcesPath); } From 2ac9be7cee3e64184c3452119ece9a10454997b0 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 2 Aug 2024 10:23:49 +0530 Subject: [PATCH 45/47] Remove unwanted comment --- .../ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java index 553991f939..d74f0e0230 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/oas/ServiceOasGenerator.java @@ -84,7 +84,6 @@ public void perform(SyntaxNodeAnalysisContext context) { Optional symbol = context.semanticModel().symbol(serviceDeclarationNode); if (symbol.isEmpty()) { - // Add warning diagnostic return; } String fileName = getFileName(symbol.get().hashCode()); From 899e40fe8cf516d1028ffc198681840a23ce1f1a Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 2 Aug 2024 10:25:12 +0530 Subject: [PATCH 46/47] Return ballerina exception as it is --- .../http/api/client/endpoint/CreateSimpleHttpClient.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/client/endpoint/CreateSimpleHttpClient.java b/native/src/main/java/io/ballerina/stdlib/http/api/client/endpoint/CreateSimpleHttpClient.java index af38f44b8e..1d622df8ec 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/client/endpoint/CreateSimpleHttpClient.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/client/endpoint/CreateSimpleHttpClient.java @@ -19,6 +19,7 @@ package io.ballerina.stdlib.http.api.client.endpoint; import io.ballerina.runtime.api.values.BDecimal; +import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; @@ -156,7 +157,8 @@ public static Object createSimpleHttpClient(BObject httpClient, BMap globalPoolC httpClient.addNativeData(HttpConstants.CLIENT_ENDPOINT_CONFIG, clientEndpointConfig); return null; } catch (Exception ex) { - return HttpUtil.createHttpError(ex.getMessage(), HttpErrorType.GENERIC_CLIENT_ERROR); + return ex instanceof BError ? ex : + HttpUtil.createHttpError(ex.getMessage(), HttpErrorType.GENERIC_CLIENT_ERROR); } } From 914220f7f160b98d37b0a73fc545d1fed9206fee Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Fri, 2 Aug 2024 11:48:08 +0530 Subject: [PATCH 47/47] Update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index dc4b86aecb..5134e6ddba 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Introduce default status code response record](https://github.com/ballerina-platform/ballerina-library/issues/6491) - [Add connection eviction feature to handle connections that receive GO_AWAY from the client](https://github.com/ballerina-platform/ballerina-library/issues/6734) - [Enhanced the configurability of Ballerina access logging by introducing multiple configuration options.](https://github.com/ballerina-platform/ballerina-library/issues/6111) +- [Introduce HTTP service contract object type](https://github.com/ballerina-platform/ballerina-library/issues/6378) ### Fixed