Skip to content

Commit

Permalink
Refactor base path logic
Browse files Browse the repository at this point in the history
  • Loading branch information
TharmiganK committed Jun 17, 2024
1 parent d71adab commit cd3d602
Show file tree
Hide file tree
Showing 10 changed files with 42 additions and 372 deletions.
15 changes: 4 additions & 11 deletions ballerina/http_annotation.bal
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};
Expand All @@ -37,6 +38,7 @@ public type HttpServiceConfig record {|
byte[] openApiDefinition = [];
boolean validation = true;
typedesc<ServiceContract> serviceType?;
string basePath?;
|};

# Configurations for CORS support.
Expand Down Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -64,9 +62,7 @@ private List<CodeAction> getCodeActions() {
new AddResponseCacheConfigCodeAction(),
new AddInterceptorResourceMethodCodeAction(),
new AddInterceptorRemoteMethodCodeAction(),
new ImplementServiceContract(),
new AddBasePathCodeAction(),
new ChangeBasePathCodeAction()
new ImplementServiceContract()
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 " +
Expand All @@ -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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -102,10 +98,8 @@ public void perform(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) {
Optional<TypeDescriptorNode> serviceTypeDesc = getServiceContractTypeDesc(
syntaxNodeAnalysisContext.semanticModel(), serviceDeclarationNode);

if (serviceTypeDesc.isPresent() && !validateBasePathFromServiceType(syntaxNodeAnalysisContext,
serviceTypeDesc.get(), serviceDeclarationNode)) {
return;
}
serviceTypeDesc.ifPresent(typeDescriptorNode ->
checkBasePathExistence(syntaxNodeAnalysisContext, serviceDeclarationNode));

Optional<MetadataNode> metadataNodeOptional = serviceDeclarationNode.metadata();
metadataNodeOptional.ifPresent(metadataNode -> validateServiceAnnotation(syntaxNodeAnalysisContext,
Expand Down Expand Up @@ -182,98 +176,12 @@ private static Set<String> extractMethodsFromServiceType(TypeDescriptorNode serv
return serviceObjTypeSymbol.methods().keySet();
}

private static boolean validateBasePathFromServiceType(SyntaxNodeAnalysisContext ctx,
TypeDescriptorNode serviceTypeDesc,
ServiceDeclarationNode serviceDeclarationNode) {
SemanticModel semanticModel = ctx.semanticModel();
Optional<Symbol> 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<AnnotationAttachmentSymbol> serviceTypeInfo = serviceType.annotAttachments().stream().filter(
annotationAttachment -> isOpenServiceTypeInfoAnnotation(annotationAttachment, semanticModel)
).findFirst();
if (serviceTypeInfo.isEmpty() || !serviceTypeInfo.get().isConstAnnotation()) {
return true;
}

Optional<String> expectedBasePathOpt = getBasePathFromServiceTypeInfo(serviceTypeInfo.get());
if (expectedBasePathOpt.isEmpty()) {
return true;
}

String expectedBasePath = expectedBasePathOpt.get().trim();

private static void checkBasePathExistence(SyntaxNodeAnalysisContext ctx,
ServiceDeclarationNode serviceDeclarationNode) {
NodeList<Node> 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<Node> 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<Symbol> serviceTypeInfo = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY,
SERVICE_CONTRACT_CONFIG);
Optional<TypeSymbol> 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<String> getBasePathFromServiceTypeInfo(AnnotationAttachmentSymbol serviceTypeInfo) {
Optional<ConstantValue> 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,
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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));
}
}
}
Expand Down Expand Up @@ -506,8 +400,8 @@ private static void validateAnnotationUsageForServiceContractType(SyntaxNodeAnal
}

protected static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext ctx,
Optional<MappingConstructorExpressionNode> 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, "");
Expand All @@ -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());
}
}
}
Expand Down Expand Up @@ -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<Node> nodes) {
private static void reportBasePathNotAllowed(SyntaxNodeAnalysisContext ctx, NodeList<Node> 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,
Expand Down
Loading

0 comments on commit cd3d602

Please sign in to comment.