Skip to content

Commit

Permalink
Merge pull request #1734 from ballerina-platform/oas-gen-svc-type
Browse files Browse the repository at this point in the history
Add service contract type support in OpenAPI tool
  • Loading branch information
TharmiganK authored Aug 2, 2024
2 parents d47c045 + 9d8c6b7 commit fb4263f
Show file tree
Hide file tree
Showing 101 changed files with 1,750 additions and 1,650 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/build-ballerina-to-openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Build
on:
workflow_dispatch:

jobs:
build:
runs-on: ubuntu-latest
if: github.repository_owner == 'ballerina-platform'
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 17.0.7
- name: Change to Timestamped Version
run: |
startTime=$(TZ="Asia/Kolkata" date +'%Y%m%d-%H%M00')
latestCommit=$(git log -n 1 --pretty=format:"%h")
VERSION=$((grep -w 'ballerinaToOpenAPIVersion' | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev)
updatedVersion=$VERSION-$startTime-$latestCommit
echo $updatedVersion
sed -i "s/ballerinaToOpenAPIVersion=\(.*\)/ballerinaToOpenAPIVersion=$updatedVersion/g" gradle.properties
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
env:
packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }}
packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }}
nexusUser: ${{ secrets.NEXUS_USERNAME }}
nexusPassword: ${{ secrets.NEXUS_PASSWORD }}
run: |
./gradlew clean build :ballerina-to-openapi:publish --stacktrace --scan --console=plain
48 changes: 48 additions & 0 deletions .github/workflows/publish-ballerina-to-openapi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# This workflow is to publish the Ballerina to OpenAPI package to the GitHub.
# This has to be run before releasing the openapi-tool, since the http package
# depends on the Ballerina to OpenAPI package.
name: Publish Ballerina to OpenAPI package

on:
workflow_dispatch:

jobs:
publish-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 17.0.7
- name: Set version env variable
run: echo "VERSION=$((grep -w "ballerinaToOpenAPIVersion" | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev)" >> $GITHUB_ENV
- name: Pre release dependency version update
env:
GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }}
run: |
echo "Version: ${VERSION}"
git config user.name ${{ secrets.BALLERINA_BOT_USERNAME }}
git config user.email ${{ secrets.BALLERINA_BOT_EMAIL }}
git checkout -b ballerina-to-openapi-release-${VERSION}
sed -i 's/ballerinaToOpenAPIVersion=\(.*\)-SNAPSHOT/ballerinaToOpenAPIVersion=\1/g' gradle.properties
sed -i 's/ballerinaLangVersion=\(.*\)-SNAPSHOT/ballerinaLangVersion=\1/g' gradle.properties
sed -i 's/ballerinaLangVersion=\(.*\)-[0-9]\{8\}-[0-9]\{6\}-.*$/ballerinaLangVersion=\1/g' gradle.properties
git add gradle.properties
git commit -m "Move dependencies to stable version" || echo "No changes to commit"
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Publish artifact
env:
GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }}
packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }}
packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }}
run: |
./gradlew :ballerina-to-openapi:publish
- name: Post release PR
env:
GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }}
run: |
curl -fsSL https://github.com/github/hub/raw/master/script/get | bash -s 2.14.1
bin/hub pull-request -m "[Automated] Sync master after ballerina-to-openapi:$VERSION release"
14 changes: 12 additions & 2 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# This workflow is to publish the openapi-tool packages except Ballerina to OpenAPI package,
# to ballerina central, maven central and GitHub. Having a released version of Ballerina to OpenAPI
# package is a prerequisite for this workflow.
name: Publish release

on:
Expand All @@ -13,9 +16,16 @@ jobs:
with:
distribution: 'adopt'
java-version: 17.0.7
- name: Check for Ballerina to OpenAPI release
run: |
BAL_TO_OPENAPI_VERSION=$(grep -w "ballerinaToOpenAPIVersion" gradle.properties | cut -d= -f2)
if [[ $BAL_TO_OPENAPI_VERSION == *"-SNAPSHOT" ]]; then
echo "Ballerina to OpenAPI version - $BAL_TO_OPENAPI_VERSION is a snapshot version. Please release the Ballerina to OpenAPI package first."
exit 1
fi
- name: Set version env variable
run: echo "VERSION=$((grep -w "version" | cut -d= -f2) < gradle.properties | rev | cut --complement -d- -f1 | rev)" >> $GITHUB_ENV
- name: Pre release depenency version update
- name: Pre release dependency version update
env:
GITHUB_TOKEN: ${{ secrets.BALLERINA_BOT_TOKEN }}
run: |
Expand Down Expand Up @@ -45,7 +55,7 @@ jobs:
nexusPassword: ${{ secrets.NEXUS_PASSWORD }}
run: |
./gradlew release -Prelease.useAutomaticVersion=true
./gradlew -Pversion=${VERSION} publish -x test -PpublishToCentral=true
./gradlew -Pversion=${VERSION} publish -x test -PpublishToCentral=true -x :ballerina-to-openapi:publish
- name: Create Github release from the release tag
run: |
curl --request POST 'https://api.github.com/repos/ballerina-platform/openapi-tools/releases' \
Expand Down
10 changes: 9 additions & 1 deletion ballerina-to-openapi/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -133,12 +133,20 @@ compileJava {
}
}

def githubReleaseVersion = project.ballerinaToOpenAPIVersion
if (githubReleaseVersion.endsWith('-SNAPSHOT')) {
def date = new Date()
def formattedDate = date.format('yyyyMMdd-HHmmss', TimeZone.getTimeZone('Asia/Kolkata'))
def lastCommitId = 'git rev-parse --short HEAD'.execute().text.trim()
githubReleaseVersion = githubReleaseVersion.replace("-SNAPSHOT", "-${formattedDate}-${lastCommitId}")
}

publishing {
publications {
mavenJava(MavenPublication) {
groupId "io.ballerina.openapi"
artifactId "ballerina-to-openapi"
version = project.version
version = githubReleaseVersion
artifact jar
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public final class Constants {
public static final String HTTP = "http";
public static final String BALLERINA = "ballerina";
public static final String EMPTY = "";
public static final String HTTP_SERVICE_CONTRACT_INFO = "ServiceContractInformation";
public static final String OPENAPI_DEFINITION = "openApiDefinition";
public static final String HTTP_SERVICE_CONTRACT = "ServiceContract";
public static final String INTERCEPTABLE_SERVICE = "InterceptableService";
public static final String NEXT_SERVICE = "NextService";
public static final String INTERCEPTOR = "Interceptor";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@
import io.ballerina.compiler.syntax.tree.NodeList;
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.SpecificFieldNode;
import io.ballerina.compiler.syntax.tree.SyntaxKind;
import io.ballerina.openapi.service.mapper.diagnostic.DiagnosticMessages;
import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic;
import io.ballerina.openapi.service.mapper.diagnostic.OpenAPIMapperDiagnostic;
import io.ballerina.openapi.service.mapper.model.OASResult;
import io.ballerina.openapi.service.mapper.model.OpenAPIInfo;
import io.ballerina.openapi.service.mapper.model.ServiceNode;
import io.ballerina.openapi.service.mapper.utils.MapperCommonUtils;
import io.ballerina.tools.diagnostics.Location;
import io.swagger.v3.oas.models.OpenAPI;
Expand Down Expand Up @@ -94,12 +94,12 @@ public final class InfoMapper {
* @param ballerinaFilePath Ballerina file path.
* @return {@code OASResult}
*/
static OASResult getOASResultWithInfo(ServiceDeclarationNode serviceNode, SemanticModel semanticModel,
String openapiFileName, Path ballerinaFilePath) {
static OASResult getOASResultWithInfo(ServiceNode serviceNode, SemanticModel semanticModel, String openapiFileName,
Path ballerinaFilePath) {
Optional<MetadataNode> metadata = serviceNode.metadata();
List<OpenAPIMapperDiagnostic> diagnostics = new ArrayList<>();
OpenAPI openAPI = new OpenAPI();
String currentServiceName = ServersMapper.getServiceBasePath(serviceNode);
String currentServiceName = serviceNode.absoluteResourcePath();
// 01. Set openAPI inFo section wit package details
String version = getContractVersion(serviceNode, semanticModel);
if (metadata.isPresent() && !metadata.get().annotations().isEmpty()) {
Expand Down Expand Up @@ -171,8 +171,8 @@ private static OASResult normalizeInfoSection(String openapiFileName, String cur
}

// Set contract version by default using package version.
private static String getContractVersion(ServiceDeclarationNode serviceDefinition, SemanticModel semanticModel) {
Optional<Symbol> symbol = semanticModel.symbol(serviceDefinition);
private static String getContractVersion(ServiceNode serviceDefinition, SemanticModel semanticModel) {
Optional<Symbol> symbol = serviceDefinition.getSymbol(semanticModel);
String version = "1.0.0";
if (symbol.isPresent()) {
Symbol serviceSymbol = symbol.get();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
* 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@
import io.ballerina.compiler.api.symbols.Documentable;
import io.ballerina.compiler.api.symbols.Documentation;
import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.ResourcePathParameterNode;
import io.ballerina.compiler.syntax.tree.Token;
import io.ballerina.openapi.service.mapper.diagnostic.DiagnosticMessages;
import io.ballerina.openapi.service.mapper.diagnostic.ExceptionDiagnostic;
import io.ballerina.openapi.service.mapper.model.AdditionalData;
import io.ballerina.openapi.service.mapper.model.OperationInventory;
import io.ballerina.openapi.service.mapper.model.ResourceFunction;
import io.ballerina.openapi.service.mapper.parameter.ParameterMapper;
import io.ballerina.openapi.service.mapper.parameter.ParameterMapperException;
import io.ballerina.openapi.service.mapper.response.ResponseMapper;
Expand Down Expand Up @@ -56,13 +56,13 @@ public class ResourceMapperImpl implements ResourceMapper {
private final Paths pathObject = new Paths();
private final AdditionalData additionalData;
private final OpenAPI openAPI;
private final List<FunctionDefinitionNode> resources;
private final List<ResourceFunction> resources;
private final ServiceMapperFactory serviceMapperFactory;

/**
* Initializes a resource parser for openApi.
*/
ResourceMapperImpl(OpenAPI openAPI, List<FunctionDefinitionNode> resources, AdditionalData additionalData,
ResourceMapperImpl(OpenAPI openAPI, List<ResourceFunction> resources, AdditionalData additionalData,
ServiceMapperFactory serviceMapperFactory) {
this.openAPI = openAPI;
this.resources = resources;
Expand All @@ -71,15 +71,15 @@ public class ResourceMapperImpl implements ResourceMapper {
}

public void setOperation() {
for (FunctionDefinitionNode resource : resources) {
for (ResourceFunction resource : resources) {
addResourceMapping(resource);
}
openAPI.setPaths(pathObject);
}

private void addResourceMapping(FunctionDefinitionNode resource) {
private void addResourceMapping(ResourceFunction resource) {
String path = MapperCommonUtils.unescapeIdentifier(generateRelativePath(resource));
String httpMethod = resource.functionName().toString().trim();
String httpMethod = resource.functionName();
if (httpMethod.equals(String.format("'%s", DEFAULT)) || httpMethod.equals(DEFAULT)) {
ExceptionDiagnostic error = new ExceptionDiagnostic(DiagnosticMessages.OAS_CONVERTOR_100,
resource.location());
Expand Down Expand Up @@ -158,7 +158,7 @@ private void addPathItem(String httpMethod, Paths path, Operation operation, Str
*
* @return Operation Adaptor object of given resource
*/
private Optional<OperationInventory> convertResourceToOperation(FunctionDefinitionNode resourceFunction,
private Optional<OperationInventory> convertResourceToOperation(ResourceFunction resourceFunction,
String httpMethod, String generateRelativePath) {
OperationInventory operationInventory = new OperationInventory();
operationInventory.setHttpOperation(httpMethod);
Expand All @@ -185,12 +185,12 @@ private Optional<OperationInventory> convertResourceToOperation(FunctionDefiniti
/**
* Filter the API documentations from resource function node.
*/
private Map<String, String> listAPIDocumentations(FunctionDefinitionNode resource,
private Map<String, String> listAPIDocumentations(ResourceFunction resource,
OperationInventory operationInventory) {

Map<String, String> apiDocs = new HashMap<>();
if (resource.metadata().isPresent()) {
Optional<Symbol> resourceSymbol = additionalData.semanticModel().symbol(resource);
Optional<Symbol> resourceSymbol = resource.getSymbol(additionalData.semanticModel());
if (resourceSymbol.isPresent()) {
Symbol symbol = resourceSymbol.get();
Optional<Documentation> documentation = ((Documentable) symbol).documentation();
Expand All @@ -208,7 +208,7 @@ private Map<String, String> listAPIDocumentations(FunctionDefinitionNode resourc
return apiDocs;
}

private String generateRelativePath(FunctionDefinitionNode resource) {
private String generateRelativePath(ResourceFunction resource) {

StringBuilder relativePath = new StringBuilder();
relativePath.append("/");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
* 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
Expand All @@ -20,6 +20,7 @@
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.NodeList;
import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode;
import io.ballerina.openapi.service.mapper.model.ServiceDeclaration;
import io.ballerina.openapi.service.mapper.utils.MapperCommonUtils;

/**
Expand All @@ -34,7 +35,10 @@ public interface ServersMapper {
*
* @param serviceDefinition The mapper definition node.
* @return The base path.
* @deprecated Construct the {@link io.ballerina.openapi.service.mapper.model.ServiceDeclaration} instance from the
* serviceDefinition and use {@link ServiceDeclaration#absoluteResourcePath()} method to get the base path.
*/
@Deprecated(forRemoval = true, since = "2.1.0")
static String getServiceBasePath(ServiceDeclarationNode serviceDefinition) {
StringBuilder currentServiceName = new StringBuilder();
NodeList<Node> serviceNameNodes = serviceDefinition.absoluteResourcePath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@
import io.ballerina.compiler.syntax.tree.ParenthesizedArgList;
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.SpecificFieldNode;
import io.ballerina.compiler.syntax.tree.SyntaxKind;
import io.ballerina.openapi.service.mapper.model.ServiceNode;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.servers.ServerVariable;
Expand All @@ -57,16 +57,21 @@ public class ServersMapperImpl implements ServersMapper {

final OpenAPI openAPI;
final Set<ListenerDeclarationNode> endpoints;
final ServiceDeclarationNode service;
final ServiceNode service;

public ServersMapperImpl(OpenAPI openAPI, Set<ListenerDeclarationNode> endpoints,
ServiceDeclarationNode service) {
public ServersMapperImpl(OpenAPI openAPI, Set<ListenerDeclarationNode> endpoints, ServiceNode service) {
this.openAPI = openAPI;
this.endpoints = endpoints;
this.service = service;
}

public void setServers() {
if (service.kind().equals(ServiceNode.Kind.SERVICE_OBJECT_TYPE)) {
Server defaultServer = getDefaultServerWithBasePath(service.absoluteResourcePath());
openAPI.setServers(Collections.singletonList(defaultServer));
return;
}

extractServerForExpressionNode();
List<Server> servers = openAPI.getServers();
//Handle ImplicitNewExpressionNode in listener
Expand All @@ -90,12 +95,12 @@ private void updateServerDetails(List<Server> servers, ListenerDeclarationNode e
if (expNode instanceof QualifiedNameReferenceNode refNode) {
//Handle QualifiedNameReferenceNode in listener
if (refNode.identifier().text().trim().equals(endPoint.variableName().text().trim())) {
String serviceBasePath = ServersMapper.getServiceBasePath(service);
String serviceBasePath = service.absoluteResourcePath();
Server server = extractServer(endPoint, serviceBasePath);
servers.add(server);
}
} else if (expNode.toString().trim().equals(endPoint.variableName().text().trim())) {
String serviceBasePath = ServersMapper.getServiceBasePath(service);
String serviceBasePath = service.absoluteResourcePath();
Server server = extractServer(endPoint, serviceBasePath);
servers.add(server);
}
Expand Down Expand Up @@ -152,7 +157,7 @@ private static Optional<ParenthesizedArgList> extractListenerNodeType(Node expre
// Function to handle ExplicitNewExpressionNode in listener.
private void extractServerForExpressionNode() {
SeparatedNodeList<ExpressionNode> bTypeExplicit = service.expressions();
String serviceBasePath = ServersMapper.getServiceBasePath(service);
String serviceBasePath = service.absoluteResourcePath();
Optional<ParenthesizedArgList> list;
List<Server> servers = new ArrayList<>();
for (ExpressionNode expressionNode: bTypeExplicit) {
Expand Down Expand Up @@ -266,4 +271,22 @@ private static String concatenateServerURL(String host, SeparatedNodeList<Mappin
}
return host;
}

private static Server getDefaultServerWithBasePath(String serviceBasePath) {
String serverUrl = String.format("http://{server}:{port}%s", serviceBasePath);
ServerVariables serverVariables = new ServerVariables();

ServerVariable serverUrlVariable = new ServerVariable();
serverUrlVariable._default("localhost");
serverVariables.addServerVariable(SERVER, serverUrlVariable);

ServerVariable portVariable = new ServerVariable();
portVariable._default("8080");
serverVariables.addServerVariable(PORT, portVariable);

Server server = new Server();
server.setUrl(serverUrl);
server.setVariables(serverVariables);
return server;
}
}
Loading

0 comments on commit fb4263f

Please sign in to comment.