Skip to content

Commit

Permalink
Merge pull request #1717 from lnash94/2_0_x_add_contact_details_oas
Browse files Browse the repository at this point in the history
[2.0.x] Add missing service meta info for the openAPI spec generation
  • Loading branch information
lnash94 authored Jun 13, 2024
2 parents f7686b8 + 08a0613 commit fa97b5f
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -326,9 +326,18 @@ public String toString() {
public static final String FALSE = "false";
public static final String SLASH = "/";
public static final String HYPHEN = "-";

//`@openapi:ServiceInfo` annotation constants
public static final String CONTRACT = "contract";
public static final String VERSION = "version";
public static final String TITLE = "title";
public static final String EMAIL = "email";
public static final String DESCRIPTION = "description";
public static final String CONTACT_NAME = "contactName";
public static final String CONTACT_URL = "contactURL";
public static final String LICENSE_NAME = "licenseName";
public static final String LICENSE_URL = "licenseURL";
public static final String TERMS_OF_SERVICE = "termsOfService";
public static final String OPENAPI_ANNOTATION = "openapi:ServiceInfo";

//File extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@
import io.ballerina.openapi.service.mapper.utils.MapperCommonUtils;
import io.ballerina.tools.diagnostics.Location;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;

import java.io.File;
import java.io.IOException;
Expand All @@ -51,10 +53,17 @@
import java.util.Locale;
import java.util.Optional;

import static io.ballerina.openapi.service.mapper.Constants.CONTACT_NAME;
import static io.ballerina.openapi.service.mapper.Constants.CONTACT_URL;
import static io.ballerina.openapi.service.mapper.Constants.CONTRACT;
import static io.ballerina.openapi.service.mapper.Constants.DESCRIPTION;
import static io.ballerina.openapi.service.mapper.Constants.EMAIL;
import static io.ballerina.openapi.service.mapper.Constants.LICENSE_NAME;
import static io.ballerina.openapi.service.mapper.Constants.LICENSE_URL;
import static io.ballerina.openapi.service.mapper.Constants.OPENAPI_ANNOTATION;
import static io.ballerina.openapi.service.mapper.Constants.SLASH;
import static io.ballerina.openapi.service.mapper.Constants.SPECIAL_CHAR_REGEX;
import static io.ballerina.openapi.service.mapper.Constants.TERMS_OF_SERVICE;
import static io.ballerina.openapi.service.mapper.Constants.TITLE;
import static io.ballerina.openapi.service.mapper.Constants.VERSION;

Expand Down Expand Up @@ -202,31 +211,66 @@ private static String normalizeTitle(String title) {
private static OASResult parseServiceInfoAnnotationAttachmentDetails(List<OpenAPIMapperDiagnostic> diagnostics,
AnnotationNode annotation,
Path ballerinaFilePath) {

Location location = annotation.location();
OpenAPI openAPI = new OpenAPI();
Optional<MappingConstructorExpressionNode> content = annotation.annotValue();
// If contract path there
if (content.isPresent()) {
SeparatedNodeList<MappingFieldNode> fields = content.get().fields();
if (!fields.isEmpty()) {
OpenAPIInfo openAPIInfo = updateOpenAPIInfoModel(fields);
// If in case ballerina file path is getting null, then openAPI specification will be generated for
// given services.
if (openAPIInfo.getContractPath().isPresent() && ballerinaFilePath != null) {
return updateExistingContractOpenAPI(diagnostics, location, openAPIInfo, ballerinaFilePath);
} else if (openAPIInfo.getTitle().isPresent() && openAPIInfo.getVersion().isPresent()) {
openAPI.setInfo(new Info().version(openAPIInfo.getVersion().get()).title(normalizeTitle
(openAPIInfo.getTitle().get())));
} else if (openAPIInfo.getVersion().isPresent()) {
openAPI.setInfo(new Info().version(openAPIInfo.getVersion().get()));
} else if (openAPIInfo.getTitle().isPresent()) {
openAPI.setInfo(new Info().title(normalizeTitle(openAPIInfo.getTitle().get())));
}
}
Optional<MappingConstructorExpressionNode> svcInfoAnnotationValue = annotation.annotValue();

if (svcInfoAnnotationValue.isEmpty()) {
return new OASResult(openAPI, diagnostics);
}

SeparatedNodeList<MappingFieldNode> fields = svcInfoAnnotationValue.get().fields();
if (fields.isEmpty()) {
return new OASResult(openAPI, diagnostics);
}

OpenAPIInfo openAPIInfo = updateOpenAPIInfoModel(fields);

// Check for contract path and existing Ballerina file path
if (openAPIInfo.getContractPath().isPresent() && ballerinaFilePath != null) {
return updateExistingContractOpenAPI(diagnostics, location, openAPIInfo, ballerinaFilePath);
}
populateOASInfo(openAPI, openAPIInfo);
return new OASResult(openAPI, diagnostics);
}

private static void populateOASInfo(OpenAPI openAPI, OpenAPIInfo openAPIInfo) {
// Populate OpenAPI Info object
Info info = openAPI.getInfo() == null ? new Info() : openAPI.getInfo();
openAPIInfo.getTitle().ifPresent(title -> info.setTitle(normalizeTitle(title)));
openAPIInfo.getVersion().ifPresent(info::setVersion);
openAPIInfo.getEmail().ifPresent(email -> {
Contact contact = (info.getContact() != null) ? info.getContact() : new Contact();
contact.setEmail(email);
info.setContact(contact);
});
openAPIInfo.getContactName().ifPresent(name -> {
Contact contact = (info.getContact() != null) ? info.getContact() : new Contact();
contact.setName(name);
info.setContact(contact);
});
openAPIInfo.getContactURL().ifPresent(url -> {
Contact contact = (info.getContact() != null) ? info.getContact() : new Contact();
contact.setUrl(url);
info.setContact(contact);
});
openAPIInfo.getLicenseURL().ifPresent(url -> {
License license = (info.getLicense() != null) ? info.getLicense() : new License();
license.setUrl(url);
info.setLicense(license);
});
openAPIInfo.getLicenseName().ifPresent(name -> {
License license = (info.getLicense() != null) ? info.getLicense() : new License();
license.setName(name);
info.setLicense(license);
});
openAPIInfo.getTermsOfService().ifPresent(info::setTermsOfService);
openAPIInfo.getDescription().ifPresent(info::setDescription);
openAPI.setInfo(info);
}


private static OASResult updateExistingContractOpenAPI(List<OpenAPIMapperDiagnostic> diagnostics,
Location location, OpenAPIInfo openAPIInfo,
Path ballerinaFilePath) {
Expand All @@ -237,21 +281,9 @@ private static OASResult updateExistingContractOpenAPI(List<OpenAPIMapperDiagnos
return oasResult;
}
OpenAPI openAPI = contract.get();
if (openAPIInfo.getVersion().isPresent() && openAPIInfo.getTitle().isPresent()) {
// read the openapi
openAPI.getInfo().setVersion(openAPIInfo.getVersion().get());
openAPI.getInfo().setTitle(openAPIInfo.getTitle().get());
diagnostics.addAll(oasResult.getDiagnostics());
return new OASResult(openAPI, oasResult.getDiagnostics());
} else if (openAPIInfo.getTitle().isPresent()) {
openAPI.getInfo().setTitle(openAPIInfo.getTitle().get());
return new OASResult(openAPI, oasResult.getDiagnostics());
} else if (openAPIInfo.getVersion().isPresent()) {
openAPI.getInfo().setVersion(openAPIInfo.getVersion().get());
return new OASResult(openAPI, oasResult.getDiagnostics());
} else {
return oasResult;
}
diagnostics.addAll(oasResult.getDiagnostics());
populateOASInfo(openAPI, openAPIInfo);
return new OASResult(openAPI, diagnostics);
}

private static OpenAPIInfo updateOpenAPIInfoModel(SeparatedNodeList<MappingFieldNode> fields) {
Expand All @@ -264,19 +296,21 @@ private static OpenAPIInfo updateOpenAPIInfoModel(SeparatedNodeList<MappingField
ExpressionNode expressionNode = value.get();
if (!expressionNode.toString().trim().isBlank()) {
fieldValue = expressionNode.toString().trim().replaceAll("\"", "");
if (!fieldValue.isBlank()) {
switch (fieldName) {
case CONTRACT:
infoBuilder.contractPath(fieldValue);
break;
case TITLE:
infoBuilder.title(fieldValue);
break;
case VERSION:
infoBuilder.version(fieldValue);
break;
default:
break;
if (fieldValue.isBlank()) {
continue;
}
switch (fieldName) {
case CONTRACT -> infoBuilder.contractPath(fieldValue);
case TITLE -> infoBuilder.title(fieldValue);
case VERSION -> infoBuilder.version(fieldValue);
case EMAIL -> infoBuilder.email(fieldValue);
case DESCRIPTION -> infoBuilder.description(fieldValue);
case CONTACT_NAME -> infoBuilder.contactName(fieldValue);
case CONTACT_URL -> infoBuilder.contactURL(fieldValue);
case TERMS_OF_SERVICE -> infoBuilder.termsOfService(fieldValue);
case LICENSE_NAME -> infoBuilder.licenseName(fieldValue);
case LICENSE_URL -> infoBuilder.licenseURL(fieldValue);
default -> {
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,25 @@ public class OpenAPIInfo {
private final String title;
private final String version;
private final String contractPath;
private final String description;
private final String email;
private final String contactName;
private final String contactURL;
private final String termsOfService;
private final String licenseName;
private final String licenseURL;

public OpenAPIInfo(OpenAPIInfoBuilder openAPIInfoBuilder) {
this.title = openAPIInfoBuilder.title;
this.version = openAPIInfoBuilder.version;
this.contractPath = openAPIInfoBuilder.contractPath;
this.description = openAPIInfoBuilder.description;
this.email = openAPIInfoBuilder.email;
this.contactName = openAPIInfoBuilder.contactName;
this.contactURL = openAPIInfoBuilder.contactURL;
this.termsOfService = openAPIInfoBuilder.termsOfService;
this.licenseName = openAPIInfoBuilder.licenseName;
this.licenseURL = openAPIInfoBuilder.licenseURL;
}

public Optional<String> getTitle() {
Expand All @@ -49,13 +63,48 @@ public Optional<String> getContractPath() {
return Optional.ofNullable(this.contractPath);
}

public Optional<String> getDescription() {
return Optional.ofNullable(this.description);
}

public Optional<String> getEmail() {
return Optional.ofNullable(this.email);
}

public Optional<String> getContactName() {
return Optional.ofNullable(this.contactName);
}

public Optional<String> getContactURL() {
return Optional.ofNullable(this.contactURL);
}

public Optional<String> getTermsOfService() {
return Optional.ofNullable(this.termsOfService);
}

public Optional<String> getLicenseName() {
return Optional.ofNullable(this.licenseName);
}

public Optional<String> getLicenseURL() {
return Optional.ofNullable(this.licenseURL);
}

/**
* This is the builder class for the {@link OpenAPIInfo}.
*/
public static class OpenAPIInfoBuilder {
private String title;
private String version;
private String contractPath;
private String description;
private String email;
private String contactName;
private String contactURL;
private String termsOfService;
private String licenseName;
private String licenseURL;

public OpenAPIInfoBuilder title(String title) {
this.title = title;
Expand All @@ -67,8 +116,44 @@ public OpenAPIInfoBuilder version(String version) {
return this;
}

public void contractPath(String contractPath) {
public OpenAPIInfoBuilder contractPath(String contractPath) {
this.contractPath = contractPath;
return this;
}

public OpenAPIInfoBuilder description(String description) {
this.description = description;
return this;
}

public OpenAPIInfoBuilder email(String email) {
this.email = email;
return this;
}

public OpenAPIInfoBuilder contactName(String contactName) {
this.contactName = contactName;
return this;
}

public OpenAPIInfoBuilder contactURL(String contactURL) {
this.contactURL = contactURL;
return this;
}

public OpenAPIInfoBuilder termsOfService(String termsOfService) {
this.termsOfService = termsOfService;
return this;
}

public OpenAPIInfoBuilder licenseName(String licenseName) {
this.licenseName = licenseName;
return this;
}

public OpenAPIInfoBuilder licenseURL(String licenseURL) {
this.licenseURL = licenseURL;
return this;
}

public OpenAPIInfo build() {
Expand Down
6 changes: 3 additions & 3 deletions module-ballerina-openapi/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
org= "ballerina"
name= "openapi"
version= "1.9.0"
version= "@toml.version@"

[[platform.java17.dependency]]
path = "../openapi-validator/build/libs/openapi-validator-1.9.0-SNAPSHOT.jar"
path = "../openapi-validator/build/libs/openapi-validator-@project.version@.jar"
groupId = "ballerina"
artifactId = "openapi"
version = "1.9.0-SNAPSHOT"
version = "@project.version@"
4 changes: 2 additions & 2 deletions module-ballerina-openapi/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ id = "openapi-tools"
class = "io.ballerina.openapi.validator.OpenAPIValidatorPlugin"

[[dependency]]
path = "../openapi-validator/build/libs/openapi-validator-1.9.0-SNAPSHOT.jar"
path = "../openapi-validator/build/libs/openapi-validator-@project.version@"
groupId = "ballerina"
artifactId = "openapi"
version = "1.9.0-SNAPSHOT"
version = "@project.version@"
14 changes: 14 additions & 0 deletions module-ballerina-openapi/annotation.bal
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
# + embed - Enable auto-inject of OpenAPI documentation to current service
# + title - Title for generated OpenAPI contract
# + version - Version for generated OpenAPI contract
# + description - A brief description of the API, outlining its purpose, features, and any other relevant details that help users understand what the API does and how to use it.
# + email - The email address to contact the API provider or support.
# + contactName - The full name of the person or organization responsible for the API.
# + contactURL - The URL to a web page with more information about the API, the provider, or support.
# + termOfService - The URL to the terms of service for the API.
# + licenseName - The name of the license under which the API is provided.
# + licenseURL - The URL to the full text of the license.
public type ServiceInformation record {|
string contract = "";
string[]? tags = [];
Expand All @@ -34,6 +41,13 @@ public type ServiceInformation record {|
boolean embed = false;
string title?;
string version?;
string description?;
string email?;
string contactName?;
string contactURL?;
string termsOfService?;
string licenseName?;
string licenseURL?;
|};

// # Client configurations code.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,12 @@ public void nonOpenAPIAnnotationWithWithoutBasePath() throws IOException, Interr
"project_non_openapi_annotation_without_base_path/result.yaml");
}

@Test(description = "Service is with openapi annotation include all oas infor section details")
public void openAPInForSectionTest() throws IOException, InterruptedException {
executeCommand("project_openapi_info/service_file.bal", "info_openapi.yaml",
"project_openapi_info/result.yaml");
}

@AfterClass
public void cleanUp() throws IOException {
TestUtil.cleanDistribution();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
openapi: 3.0.1
info:
title: Mock File
description: API system description
termsOfService: http://mock-api-doc
contact:
name: sumudu
url: http://mock-api-contact
email: [email protected]
license:
name: ABC
url: http://abc.com
version: 0.1.0
servers:
- url: "{server}:{port}/titleBase"
Expand Down
Loading

0 comments on commit fa97b5f

Please sign in to comment.