From 2f7e552edc68ae04e95a1dad6195a95b2a800932 Mon Sep 17 00:00:00 2001 From: chiranSachintha Date: Mon, 6 Jul 2026 14:39:45 +0530 Subject: [PATCH] Use compiler endpoint artifacts for HTTP endpoint export --- .../ServiceArtifactsExtractionTest.java | 56 ++++++++----------- compiler-plugin/build.gradle | 3 - .../generator/EndpointWrapper.java | 34 ----------- .../generator/EndpointYamlGenerator.java | 55 +++--------------- .../generator/ServiceArtifactsExtractor.java | 7 +-- .../src/main/java/module-info.java | 2 - 6 files changed, 32 insertions(+), 125 deletions(-) delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointWrapper.java diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/ServiceArtifactsExtractionTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/ServiceArtifactsExtractionTest.java index 132a9dd11..ec2134795 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/ServiceArtifactsExtractionTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/ServiceArtifactsExtractionTest.java @@ -86,19 +86,18 @@ public void testServiceArtifactGenerationWithSimpleService() throws Exception { try { executeBallerinaCommand(projectDirPath, true); - Path endpointYaml = projectDirPath.resolve("target") + Path endpointsYaml = projectDirPath.resolve("target") .resolve(ARTIFACT_DIR) - .resolve("service_endpoint.yaml"); + .resolve("endpoints.yaml"); Path openAPIYaml = projectDirPath.resolve("target") .resolve(ARTIFACT_DIR) .resolve("service_openapi.yaml"); - Assert.assertTrue(Files.exists(endpointYaml), "Endpoint YAML should be generated"); + Assert.assertTrue(Files.exists(endpointsYaml), "Endpoints YAML should be generated"); Assert.assertTrue(Files.exists(openAPIYaml), "OpenAPI YAML should be generated"); - Path expectedEndpointFile = RESOURCE_DIRECTORY.resolve("../yaml_files").resolve("service_endpoint.yaml"); Path expectedOpenAPIFile = RESOURCE_DIRECTORY.resolve("../yaml_files").resolve("service_openapi_1.yaml"); - verifyYamlContent(endpointYaml, expectedEndpointFile); + Assert.assertTrue(Files.readString(endpointsYaml).contains("schemaPath: \"service_openapi.yaml\"")); verifyYamlContent(openAPIYaml, expectedOpenAPIFile); } finally { deleteDirectories(projectDirPath); @@ -147,16 +146,16 @@ public void testServiceArtifactGenerationForMultipleServices() throws Exception yamlFiles = paths.filter(path -> path.toString().endsWith(".yaml")).sorted().toList(); } - Assert.assertEquals(yamlFiles.size(), 10, - "Expected openapi and endpoint artifacts for all services"); + Assert.assertEquals(yamlFiles.size(), 6, + "Expected OpenAPI artifacts for all services and one endpoint artifact"); Assert.assertEquals(yamlFiles.stream() .map(this::safeFileName) .filter(fileName -> fileName.contains("_openapi")) .count(), 5L, "Expected 5 OpenAPI artifact files"); Assert.assertEquals(yamlFiles.stream() .map(this::safeFileName) - .filter(fileName -> fileName.contains("_endpoint")) - .count(), 5L, "Expected 5 endpoint artifact files"); + .filter("endpoints.yaml"::equals) + .count(), 1L, "Expected one endpoint artifact file"); } finally { deleteDirectories(projectDirPath); @@ -168,22 +167,21 @@ public void testEndpointYamlGenerationWithRegularServicePath() throws Exception Path projectDirPath = RESOURCE_DIRECTORY.resolve("sample_package_48"); try { executeBallerinaCommand(projectDirPath, true); - Path endpointYaml = projectDirPath.resolve("target") + Path endpointsYaml = projectDirPath.resolve("target") .resolve(ARTIFACT_DIR) - .resolve("service_userservice_endpoint.yaml"); + .resolve("endpoints.yaml"); Path openAPIYaml = projectDirPath.resolve("target") .resolve(ARTIFACT_DIR) .resolve("service_userservice_openapi.yaml"); - Assert.assertTrue(Files.exists(endpointYaml), - "Endpoint YAML for regular base path should be generated"); + Assert.assertTrue(Files.exists(endpointsYaml), + "Endpoints YAML for regular base path should be generated"); Assert.assertTrue(Files.exists(openAPIYaml), "OpenAPI YAML for regular base path should be generated"); - Path expectedEndpointFile = RESOURCE_DIRECTORY.resolve("../yaml_files") - .resolve("service_userservice_endpoint.yaml"); Path expectedOpenAPIFile = RESOURCE_DIRECTORY.resolve("../yaml_files") .resolve("service_userservice_openapi.yaml"); - verifyYamlContent(endpointYaml, expectedEndpointFile); + Assert.assertTrue(Files.readString(endpointsYaml) + .contains("schemaPath: \"service_userservice_openapi.yaml\"")); verifyYamlContent(openAPIYaml, expectedOpenAPIFile); } finally { deleteDirectories(projectDirPath); @@ -209,8 +207,8 @@ public void testConfigurablePortWithDefaultValue() throws Exception { try { executeBallerinaCommand(projectDirPath, true); Path artifactDir = projectDirPath.resolve("target").resolve(ARTIFACT_DIR); - Path endpointYaml = artifactDir.resolve("service_endpoint.yaml"); - assertEndpointPort(endpointYaml, 8080); + Path endpointsYaml = artifactDir.resolve("endpoints.yaml"); + assertEndpointPort(endpointsYaml, 8080); } finally { deleteDirectories(projectDirPath); } @@ -226,16 +224,8 @@ public void testEndpointYamlGenerationWithEmptyServicePath() throws Exception { Path artifactDir = projectDirPath.resolve("target").resolve(ARTIFACT_DIR); Assert.assertTrue(Files.exists(artifactDir), "Artifact directory should exist"); - List endpointFiles; - try (Stream paths = Files.walk(artifactDir)) { - endpointFiles = paths - .map(this::safeFileName) - .filter(fileName -> fileName.endsWith("_endpoint.yaml")).toList(); - } - - Assert.assertEquals(endpointFiles.size(), 1, "Expected exactly one endpoint YAML file"); - Assert.assertTrue(endpointFiles.getFirst().matches("service_[0-9]+_endpoint\\.yaml"), - "Endpoint YAML file should use fallback hash-based naming for empty service path"); + Assert.assertTrue(Files.exists(artifactDir.resolve("endpoints.yaml")), + "Expected consolidated endpoints YAML file"); } finally { deleteDirectories(projectDirPath); @@ -272,7 +262,7 @@ public void testInProcessServiceArtifactGenerationWithExportEndpoints() throws E Assert.assertTrue(Files.exists(artifactDir), "Artifact directory should exist"); Assert.assertTrue(Files.exists(artifactDir.resolve("service_openapi.yaml")), "OpenAPI artifact file should be generated"); - Assert.assertTrue(Files.exists(artifactDir.resolve("service_endpoint.yaml")), + Assert.assertTrue(Files.exists(artifactDir.resolve("endpoints.yaml")), "Endpoint artifact file should be generated"); } finally { deleteDirectories(projectDirPath); @@ -306,7 +296,7 @@ public void testEndpointYamlGeneratorWithMissingPortVariableReportsDiagnostic() EndpointYamlGenerator generator = new EndpointYamlGenerator(contextData.serviceNode, context, server); Endpoint endpoint = generator.getEndpoint(); - generator.writeEndpointYaml(); + generator.addEndpointArtifact(); Assert.assertEquals(endpoint.getPort(), 0, "Endpoint port should fallback to 0 when no port variable default is provided"); @@ -333,7 +323,7 @@ public void testEndpointYamlGeneratorWithInvalidPortValue() throws IOException { EndpointYamlGenerator generator = new EndpointYamlGenerator(contextData.serviceNode, context, server); Endpoint endpoint = generator.getEndpoint(); - generator.writeEndpointYaml(); + generator.addEndpointArtifact(); Assert.assertEquals(endpoint.getPort(), 0, "Endpoint port should remain default when server variable is non-numeric"); @@ -369,12 +359,11 @@ public void testExportServiceArtifact() throws Exception { diagnostics); Path artifactDir = projectDirPath.resolve("target").resolve(ARTIFACT_DIR); - Path endpointYaml = artifactDir.resolve("service_endpoint.yaml"); Path openapiYaml = artifactDir.resolve("service_openapi.yaml"); Assert.assertTrue(Files.exists(artifactDir), "exportServiceArtifact must create target/artifact/"); - Assert.assertTrue(Files.exists(endpointYaml)); + Assert.assertEquals(project.endpointArtifacts().size(), 1); Assert.assertTrue(Files.exists(openapiYaml)); deleteDirectories(projectDirPath); } @@ -812,6 +801,7 @@ private SyntaxNodeAnalysisContext createSyntaxNodeAnalysisContext(TestContextDat } yield null; } + case "addEndpointArtifact" -> null; default -> throw new UnsupportedOperationException("Unsupported context method: " + method.getName()); }); diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle index c4a1bfcd6..b08c46098 100644 --- a/compiler-plugin/build.gradle +++ b/compiler-plugin/build.gradle @@ -42,9 +42,6 @@ dependencies { implementation group: 'io.ballerina.scan', name: 'scan-command', version: "${balScanVersion}" - compileOnly "com.fasterxml.jackson.core:jackson-databind:${jacksonDatabindVersion}" - compileOnly "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jacksonDatabindVersion}" - externalJars group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointWrapper.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointWrapper.java deleted file mode 100644 index c4d8c2c8f..000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointWrapper.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2026, 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.endpointyaml.generator; - -/* - * Represents the wrapper class to serialize the endpoint object - */ -public class EndpointWrapper { - private final Endpoint endpoint; - - public EndpointWrapper(Endpoint endpoint) { - this.endpoint = endpoint; - } - - public Endpoint getEndpoint() { - return endpoint; - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointYamlGenerator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointYamlGenerator.java index ef774993f..82fa96c58 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointYamlGenerator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/EndpointYamlGenerator.java @@ -18,14 +18,10 @@ package io.ballerina.stdlib.http.compiler.endpointyaml.generator; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; -import io.ballerina.projects.Package; -import io.ballerina.projects.Project; +import io.ballerina.projects.plugins.EndpointArtifact; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.tools.diagnostics.DiagnosticFactory; import io.ballerina.tools.diagnostics.DiagnosticInfo; @@ -33,14 +29,7 @@ import io.swagger.v3.oas.models.servers.Server; import io.swagger.v3.oas.models.servers.ServerVariables; -import java.io.IOException; import java.io.PrintStream; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -import static io.ballerina.openapi.service.mapper.utils.CodegenUtils.resolveContractFileName; public class EndpointYamlGenerator { private final ServiceDeclarationNode node; @@ -49,19 +38,16 @@ public class EndpointYamlGenerator { private static final PrintStream outStream = System.out; - private static final String ARTIFACT = "artifact"; private static final String REST = "REST"; - private static final String YAML_EXTENSION = ".yaml"; private static final String OPENAPI_SUFFIX = "_openapi"; - private static final String ENDPOINT_SUFFIX = "_endpoint"; private static final String PORT = "port"; private int portVal = 0; private final Server server; - /* - * Generates the .yaml file with endpoint details of HTTP service + /** + * Adds endpoint details of HTTP service to the build context. */ public EndpointYamlGenerator(ServiceDeclarationNode node, SyntaxNodeAnalysisContext context, Server server) { this.node = node; @@ -98,12 +84,10 @@ public Endpoint getEndpoint() { return new Endpoint(this.portVal, basePath, REST, this.schemaFileName); } - public void writeEndpointYaml() throws IOException { + public void addEndpointArtifact() { Endpoint ep = getEndpoint(); - Path outPath = resolveOutputPath(); - String fileName = buildEndpointFileName(outPath); - Path path = outPath.resolve(ARTIFACT).resolve(fileName + YAML_EXTENSION); - writeYaml(path, new EndpointWrapper(ep)); + context.addEndpointArtifact(new EndpointArtifact(getEndpointName(), ep.getPort(), ep.getBasePath(), + ep.getType(), ep.getSchemaPath())); } private String getBasePath() { @@ -115,31 +99,8 @@ private String getBasePath() { return serviceBasePath.toString(); } - private Path resolveOutputPath() throws IOException { - Package currentPackage = this.context.currentPackage(); - Project project = currentPackage.project(); - Path outPath = project.targetDir(); - Files.createDirectories(Paths.get(String.valueOf(outPath), ARTIFACT)); - return outPath; - } - - private String buildEndpointFileName(Path outPath) { - String base = schemaFileName.split("\\.")[0].replace(OPENAPI_SUFFIX, ENDPOINT_SUFFIX); - return resolveContractFileName(outPath.resolve(ARTIFACT), base, false); - } - - private void writeYaml(Path path, EndpointWrapper wrapper) throws IOException { - YAMLFactory yamlFactory = YAMLFactory.builder() - .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) - .build(); - ObjectMapper mapper = new ObjectMapper(yamlFactory); - mapper.findAndRegisterModules(); - - try (Writer writer = Files.newBufferedWriter(path)) { - mapper.writeValue(writer, wrapper); - } catch (IOException e) { - throw new IOException("Failed to write endpoint yaml to " + path, e); - } + private String getEndpointName() { + return schemaFileName.split("\\.")[0].replace(OPENAPI_SUFFIX, ""); } private static void reportMissingPortConfigDiagnostic(SyntaxNodeAnalysisContext context) { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/ServiceArtifactsExtractor.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/ServiceArtifactsExtractor.java index f10efef19..a9802da9c 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/ServiceArtifactsExtractor.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/endpointyaml/generator/ServiceArtifactsExtractor.java @@ -179,12 +179,7 @@ private void exportEndpointYaml(ServiceDeclarationNode serviceNode, SyntaxNodeAn for (Server server: servers) { EndpointYamlGenerator endpointYamlGeneratorHttp = new EndpointYamlGenerator(serviceNode, context, server); - try { - endpointYamlGeneratorHttp.writeEndpointYaml(); - } catch (IOException e) { - diagnostics.add(getDiagnostics( - new ExceptionDiagnostic(DiagnosticMessages.OAS_CONVERTOR_108, e.toString()))); - } + endpointYamlGeneratorHttp.addEndpointArtifact(); } } diff --git a/compiler-plugin/src/main/java/module-info.java b/compiler-plugin/src/main/java/module-info.java index 4897145f5..646d0db6e 100644 --- a/compiler-plugin/src/main/java/module-info.java +++ b/compiler-plugin/src/main/java/module-info.java @@ -24,8 +24,6 @@ requires io.swagger.v3.oas.models; requires io.ballerina.openapi.service; requires io.ballerina.scan; - requires com.fasterxml.jackson.dataformat.yaml; - requires com.fasterxml.jackson.databind; requires java.logging; requires io.ballerina.runtime; }