Skip to content

Commit

Permalink
Refactor service contract implementation with static resource
Browse files Browse the repository at this point in the history
  • Loading branch information
TharmiganK committed Jul 15, 2024
1 parent 16258e9 commit bec7625
Show file tree
Hide file tree
Showing 29 changed files with 490 additions and 1,279 deletions.
13 changes: 1 addition & 12 deletions ballerina/http_annotation.bal
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public type HttpServiceConfig record {|
ListenerAuthConfig[] auth?;
string mediaTypeSubtypePrefix?;
boolean treatNilableAsOptional = true;
@deprecated
byte[] openApiDefinition = [];
boolean validation = true;
typedesc<ServiceContract> serviceType?;
Expand Down Expand Up @@ -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;
10 changes: 10 additions & 0 deletions compiler-plugin/spotbugs-exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,14 @@
<Field name="serviceNode"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Record name="io.ballerina.stdlib.http.compiler.HttpServiceAnalyzer("/>
<Field name="ctxData"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Record name="io.ballerina.stdlib.http.compiler.codemodifier.HttpServiceModifier"/>
<Field name="ctxData"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
</FindBugsFilter>
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -45,8 +46,10 @@ public class HttpCompilerPlugin extends CompilerPlugin {

@Override
public void init(CompilerPluginContext context) {
context.addCodeModifier(new HttpServiceModifier());
context.addCodeAnalyzer(new HttpServiceAnalyzer());
Map<String, Object> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -309,4 +319,72 @@ private static void populateNilableBasicArrayTypes(Map<String, TypeSymbol> 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<Diagnostic> 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<Symbol> serviceSymOptional = semanticModel.symbol(node);
if (serviceSymOptional.isPresent()) {
List<TypeSymbol> 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<Symbol> serviceObjSymbol = semanticModel.symbol(serviceObjType.parent());
if (serviceObjSymbol.isEmpty() ||
(!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) {
return false;
}

Optional<Symbol> 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> ctxData;

public HttpServiceAnalyzer(Map<String, Object> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -48,8 +45,7 @@ public class HttpServiceObjTypeAnalyzer extends HttpServiceValidator {

@Override
public void perform(SyntaxNodeAnalysisContext context) {
List<Diagnostic> diagnostics = context.semanticModel().diagnostics();
if (diagnostics.stream().anyMatch(d -> DiagnosticSeverity.ERROR.equals(d.diagnosticInfo().severity()))) {
if (diagnosticContainsErrors(context)) {
return;
}

Expand All @@ -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<Symbol> serviceObjSymbol = semanticModel.symbol(serviceObjType.parent());
if (serviceObjSymbol.isEmpty() ||
(!(serviceObjSymbol.get() instanceof TypeDefinitionSymbol serviceObjTypeDef))) {
return false;
}

Optional<Symbol> 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<Symbol> serviceObjSymbol = semanticModel.symbol(serviceObjType.parent());
Expand Down
Loading

0 comments on commit bec7625

Please sign in to comment.