Skip to content

Commit

Permalink
Merge pull request #531 from LakshanWeerasinghe/impr-resource-paths
Browse files Browse the repository at this point in the history
Show resource path params as custom properties in the flow diagram
  • Loading branch information
KavinduZoysa authored Jan 13, 2025
2 parents 6cdf30c + f175c40 commit 5b08bff
Show file tree
Hide file tree
Showing 56 changed files with 6,632 additions and 5,851 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import io.ballerina.compiler.syntax.tree.CommentNode;
import io.ballerina.compiler.syntax.tree.CommitActionNode;
import io.ballerina.compiler.syntax.tree.CompoundAssignmentStatementNode;
import io.ballerina.compiler.syntax.tree.ComputedResourceAccessSegmentNode;
import io.ballerina.compiler.syntax.tree.ContinueStatementNode;
import io.ballerina.compiler.syntax.tree.DoStatementNode;
import io.ballerina.compiler.syntax.tree.ElseBlockNode;
Expand Down Expand Up @@ -119,6 +120,7 @@
import io.ballerina.flowmodelgenerator.core.model.node.XmlPayloadBuilder;
import io.ballerina.flowmodelgenerator.core.utils.CommonUtils;
import io.ballerina.flowmodelgenerator.core.utils.ParamUtils;
import io.ballerina.flowmodelgenerator.core.utils.TypeUtils;
import io.ballerina.projects.Project;
import io.ballerina.tools.text.LinePosition;
import io.ballerina.tools.text.LineRange;
Expand Down Expand Up @@ -310,10 +312,9 @@ public void visit(ClientResourceAccessActionNode clientResourceAccessActionNode)
Optional<Documentation> documentation = methodSymbol.documentation();
String description = documentation.flatMap(Documentation::description).orElse("");
SeparatedNodeList<Node> nodes = clientResourceAccessActionNode.resourceAccessPath();
String resourcePath = nodes.stream().map(Node::toSourceCode).collect(Collectors.joining("/"));
String fullPath = "/" + resourcePath;

String resourcePathTemplate = ParamUtils.buildResourcePathTemplate(methodSymbol);
ParamUtils.ResourcePathTemplate resourcePathTemplate = ParamUtils.buildResourcePathTemplate(semanticModel,
methodSymbol, semanticModel.types().ERROR);

startNode(NodeKind.RESOURCE_ACTION_CALL, expressionNode.parent())
.symbolInfo(methodSymbol)
Expand All @@ -324,17 +325,56 @@ public void visit(ClientResourceAccessActionNode clientResourceAccessActionNode)
.codedata()
.object("Client")
.symbol(methodName)
.resourcePath(resourcePathTemplate)
.resourcePath(resourcePathTemplate.resourcePathTemplate())
.stepOut()
.properties()
.callExpression(expressionNode, Property.CONNECTION_KEY)
.resourcePath(fullPath)
.data(this.typedBindingPatternNode, false, new HashSet<>());

if (TypeUtils.isHttpModule(methodSymbol)) {
String resourcePath = nodes.stream().map(Node::toSourceCode).collect(Collectors.joining("/"));
String fullPath = "/" + resourcePath;
nodeBuilder.properties().resourcePath(fullPath, true);
} else {
nodeBuilder.properties().resourcePath(resourcePathTemplate.resourcePathTemplate(), false);

int idx = 0;
for (int i = 0; i < nodes.size(); i++) {
Node node = nodes.get(i);
if (nodes.size() <= idx) {
break;
}
if (node instanceof ComputedResourceAccessSegmentNode computedResourceAccessSegmentNode) {
ExpressionNode expr = computedResourceAccessSegmentNode.expression();
ParameterResult paramResult = resourcePathTemplate.pathParams().get(idx);
String unescapedParamName = ParamUtils.removeLeadingSingleQuote(paramResult.name());
nodeBuilder.properties()
.custom()
.metadata()
.label(unescapedParamName)
.description(paramResult.description())
.stepOut()
.codedata()
.kind(paramResult.kind().name())
.originalName(paramResult.name())
.stepOut()
.value(expr.toSourceCode())
.typeConstraint(paramResult.type())
.type(Property.ValueType.EXPRESSION)
.editable()
.defaultable(paramResult.optional() == 1)
.stepOut()
.addProperty(unescapedParamName);
idx++;
}
}
}

DatabaseManager dbManager = DatabaseManager.getInstance();
ModuleID id = symbol.get().getModule().get().id();
Optional<FunctionResult> functionResult = dbManager.getAction(id.orgName(), id.moduleName(),
symbol.get().getName().get(), resourcePathTemplate, DatabaseManager.FunctionKind.RESOURCE);
symbol.get().getName().get(), resourcePathTemplate.resourcePathTemplate(),
DatabaseManager.FunctionKind.RESOURCE);

final Map<String, Node> namedArgValueMap = new HashMap<>();
final Queue<Node> positionalArgs = new LinkedList<>();
Expand Down Expand Up @@ -450,39 +490,42 @@ private void buildPropsFromFuncCallArgs(SeparatedNodeList<FunctionArgumentNode>
List<ParameterResult> functionParameters = funcParamMap.values().stream().toList();
boolean hasOnlyRestParams = functionParameters.size() == 1;
for (ParameterResult paramResult : functionParameters) {
if (paramResult.kind().equals(Parameter.Kind.PARAM_FOR_TYPE_INFER)
|| paramResult.kind().equals(Parameter.Kind.INCLUDED_RECORD)) {
Parameter.Kind paramKind = paramResult.kind();

if (paramKind.equals(Parameter.Kind.PATH_PARAM) || paramKind.equals(Parameter.Kind.PATH_REST_PARAM)
|| paramKind.equals(Parameter.Kind.PARAM_FOR_TYPE_INFER)
|| paramKind.equals(Parameter.Kind.INCLUDED_RECORD)) {
continue;
}

String unescapedParamName = ParamUtils.removeLeadingSingleQuote(paramResult.name());
Property.Builder<FormBuilder<NodeBuilder>> customPropBuilder = nodeBuilder.properties().custom();
customPropBuilder
.metadata()
.label(unescapedParamName)
.description(paramResult.description())
.stepOut()
.label(unescapedParamName)
.description(paramResult.description())
.stepOut()
.codedata()
.kind(paramResult.kind().name())
.originalName(paramResult.name())
.importStatements(paramResult.importStatements())
.stepOut()
.kind(paramKind.name())
.originalName(paramResult.name())
.importStatements(paramResult.importStatements())
.stepOut()
.placeholder(paramResult.defaultValue())
.typeConstraint(paramResult.type())
.editable()
.defaultable(paramResult.optional() == 1);

if (paramResult.kind() == Parameter.Kind.INCLUDED_RECORD_REST) {
if (paramKind == Parameter.Kind.INCLUDED_RECORD_REST) {
if (hasOnlyRestParams) {
customPropBuilder.defaultable(false);
}
customPropBuilder.type(Property.ValueType.MAPPING_EXPRESSION_SET);
} else if (paramResult.kind() == Parameter.Kind.REST_PARAMETER) {
} else if (paramKind == Parameter.Kind.REST_PARAMETER) {
if (hasOnlyRestParams) {
customPropBuilder.defaultable(false);
}
customPropBuilder.type(Property.ValueType.EXPRESSION_SET);
} else if (paramResult.kind() == Parameter.Kind.REQUIRED) {
} else if (paramKind == Parameter.Kind.REQUIRED) {
customPropBuilder.type(Property.ValueType.EXPRESSION).value(paramResult.defaultValue());
} else {
customPropBuilder.type(Property.ValueType.EXPRESSION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ public enum Kind {
REST_PARAMETER,
INCLUDED_FIELD,
PARAM_FOR_TYPE_INFER,
INCLUDED_RECORD_REST
INCLUDED_RECORD_REST,
PATH_PARAM,
PATH_REST_PARAM
}

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,11 @@ public record ParameterResult(
String description,
Integer optional,
String importStatements) {


public static ParameterResult from(String name, String type, Parameter.Kind kind, String defaultValue,
String description, Integer optional) {
return new ParameterResult(0, name, type, kind, defaultValue, description, optional,
null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import io.ballerina.flowmodelgenerator.core.model.node.ExpressionBuilder;
import io.ballerina.flowmodelgenerator.core.model.node.RemoteActionCallBuilder;
import io.ballerina.flowmodelgenerator.core.utils.CommonUtils;
import io.ballerina.flowmodelgenerator.core.utils.ParamUtils;
import io.ballerina.tools.text.LineRange;
import org.ballerinalang.langserver.common.utils.NameUtil;

Expand Down Expand Up @@ -252,15 +253,27 @@ public FormBuilder<T> callExpression(ExpressionNode expressionNode, String key)
return this;
}

public FormBuilder<T> resourcePath(String path) {
public FormBuilder<T> resourcePath(String path, boolean editable) {
propertyBuilder
.metadata()
.label(Property.RESOURCE_PATH_LABEL)
.description(Property.RESOURCE_PATH_DOC)
.stepOut()
.type(Property.ValueType.EXPRESSION)
.value(path)
.editable();
.type(Property.ValueType.EXPRESSION);
if (editable) {
propertyBuilder
.codedata()
.originalName(ParamUtils.REST_RESOURCE_PATH)
.stepOut()
.value(path)
.editable();
} else {
propertyBuilder
.codedata()
.originalName(path)
.stepOut()
.value(path.replaceAll("\\\\", ""));
}
addProperty(Property.RESOURCE_PATH_KEY);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@
import io.ballerina.flowmodelgenerator.core.model.NodeBuilder;
import io.ballerina.flowmodelgenerator.core.model.NodeKind;
import io.ballerina.flowmodelgenerator.core.model.Property;
import io.ballerina.flowmodelgenerator.core.model.PropertyCodedata;
import io.ballerina.flowmodelgenerator.core.model.SourceBuilder;
import io.ballerina.flowmodelgenerator.core.utils.CommonUtils;
import io.ballerina.flowmodelgenerator.core.utils.ParamUtils;
import org.eclipse.lsp4j.TextEdit;

import java.nio.file.Path;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -68,17 +71,50 @@ public Map<Path, List<TextEdit>> toSource(SourceBuilder sourceBuilder) {
if (connection.isEmpty()) {
throw new IllegalStateException("Client must be defined for an action call node");
}

Set<String> ignoredKeys = new HashSet<>(List.of(Property.CONNECTION_KEY, Property.VARIABLE_KEY,
Property.TYPE_KEY, TARGET_TYPE_KEY, Property.RESOURCE_PATH_KEY,
Property.CHECK_ERROR_KEY));

String resourcePath = flowNode.properties().get(Property.RESOURCE_PATH_KEY).codedata().originalName();

if (resourcePath.equals(ParamUtils.REST_RESOURCE_PATH)) {
resourcePath = flowNode.properties().get(Property.RESOURCE_PATH_KEY).value().toString();
}

Set<String> keys = new LinkedHashSet<>(flowNode.properties().keySet());
keys.removeAll(ignoredKeys);

for (String key : keys) {
Optional<Property> property = flowNode.getProperty(key);
if (property.isEmpty()) {
continue;
}
PropertyCodedata propCodedata = property.get().codedata();
if (propCodedata == null) {
continue;
}
if (propCodedata.kind().equals(Parameter.Kind.PATH_PARAM.name())) {
String pathParamSubString = "[" + key + "]";
String replacement = "[" + property.get().value().toString() + "]";
resourcePath = resourcePath.replace(pathParamSubString, replacement);
ignoredKeys.add(key);
} else if (propCodedata.kind().equals(Parameter.Kind.PATH_REST_PARAM.name())) {
String replacement = property.get().value().toString();
resourcePath = resourcePath.replace(ParamUtils.REST_PARAM_PATH, replacement);
ignoredKeys.add(key);
}
}


return sourceBuilder.token()
.name(connection.get().toSourceCode())
.keyword(SyntaxKind.RIGHT_ARROW_TOKEN)
.resourcePath(sourceBuilder.flowNode.properties().get(Property.RESOURCE_PATH_KEY).value().toString())
.resourcePath(resourcePath)
.keyword(SyntaxKind.DOT_TOKEN)
.name(sourceBuilder.flowNode.codedata().symbol())
.stepOut()
.functionParameters(flowNode,
Set.of(Property.CONNECTION_KEY, Property.VARIABLE_KEY,
Property.TYPE_KEY, TARGET_TYPE_KEY, Property.RESOURCE_PATH_KEY,
Property.CHECK_ERROR_KEY))
.functionParameters(flowNode, ignoredKeys)
.textEdit(false)
.acceptImport()
.build();
Expand Down Expand Up @@ -121,7 +157,8 @@ public void setConcreteTemplateData(TemplateContext context) {
.stepOut()
.addProperty(Property.CONNECTION_KEY);

properties().resourcePath(function.resourcePath());
String resourcePath = function.resourcePath();
properties().resourcePath(resourcePath, resourcePath.equals(ParamUtils.REST_RESOURCE_PATH));

List<ParameterResult> functionParameters = dbManager.getFunctionParameters(function.functionId());
boolean hasOnlyRestParams = functionParameters.size() == 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public static String getTypeSignature(TypeSymbol typeSymbol, ModuleInfo moduleIn

String typeName = matcher.group(4);

if (!modPart.equals(moduleInfo.packageName())) {
if (moduleInfo == null || !modPart.equals(moduleInfo.packageName())) {
newText.append(modPart);
newText.append(":");
}
Expand Down Expand Up @@ -633,7 +633,7 @@ private static void analyzeTypeSymbolForImports(Set<String> imports, TypeSymbol
String packageName = moduleId.packageName();
String moduleName = moduleId.moduleName();

if (isPredefinedLangLib(orgName, packageName) ||
if (isPredefinedLangLib(orgName, packageName) || isAnnotationLangLib(orgName, packageName) ||
isWithinCurrentModule(moduleInfo, orgName, packageName, moduleName)) {
return;
}
Expand All @@ -642,6 +642,10 @@ private static void analyzeTypeSymbolForImports(Set<String> imports, TypeSymbol
}
}

private static boolean isAnnotationLangLib(String orgName, String packageName) {
return orgName.equals(CommonUtil.BALLERINA_ORG_NAME) && packageName.equals("lang.annotations");
}

/**
* Generates the import statement of the format `<org>/<package>[.<module>]`.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,37 +54,58 @@
*/
public class ParamUtils {

public static final String REST_RESOURCE_PATH = "/path/to/subdirectory";
public static final String REST_PARAM_PATH = "/path/to/resource";
public static final String REST_RESOURCE_PATH_LABEL = "Remaining Resource Path";

/**
* Builds the resource path template for the given function symbol.
*
* @param functionSymbol the function symbol
* @return the resource path template
*/
public static String buildResourcePathTemplate(FunctionSymbol functionSymbol) {
public static ResourcePathTemplate buildResourcePathTemplate(SemanticModel semanticModel,
FunctionSymbol functionSymbol,
TypeSymbol errorTypeSymbol) {
Map<String, String> documentationMap = functionSymbol.documentation().map(Documentation::parameterMap)
.orElse(Map.of());
StringBuilder pathBuilder = new StringBuilder();
ResourceMethodSymbol resourceMethodSymbol = (ResourceMethodSymbol) functionSymbol;
ResourcePath resourcePath = resourceMethodSymbol.resourcePath();
List<ParameterResult> pathParams = new ArrayList<>();
switch (resourcePath.kind()) {
case PATH_SEGMENT_LIST -> {
PathSegmentList pathSegmentList = (PathSegmentList) resourcePath;
for (Symbol pathSegment : pathSegmentList.list()) {
pathBuilder.append("/");
if (pathSegment instanceof PathParameterSymbol pathParameterSymbol) {
String value = DefaultValueGeneratorUtil
String defaultValue = DefaultValueGeneratorUtil
.getDefaultValueForType(pathParameterSymbol.typeDescriptor());
pathBuilder.append("[").append(value).append("]");
String type = CommonUtils.getTypeSignature(semanticModel, pathParameterSymbol.typeDescriptor(),
true);
String paramName = pathParameterSymbol.getName().orElse("");
String paramDescription = documentationMap.get(paramName);
pathBuilder.append("[").append(paramName).append("]");
pathParams.add(ParameterResult.from(paramName, type, Parameter.Kind.PATH_PARAM, defaultValue,
paramDescription, 0));
} else {
pathBuilder.append(pathSegment.getName().orElse(""));
}
}
((PathSegmentList) resourcePath).pathRestParameter().ifPresent(pathRestParameter -> {
pathBuilder.append("/path/to/subdirectory");
pathParams.add(ParameterResult.from(REST_RESOURCE_PATH_LABEL, "string",
Parameter.Kind.PATH_REST_PARAM, REST_PARAM_PATH, REST_RESOURCE_PATH_LABEL, 0));
});
}
case PATH_REST_PARAM -> pathBuilder.append("/path/to/subdirectory");
case DOT_RESOURCE_PATH -> pathBuilder.append("\\.");
case PATH_REST_PARAM -> {
pathBuilder.append(REST_RESOURCE_PATH);
}
case DOT_RESOURCE_PATH -> pathBuilder.append("/");
}
return pathBuilder.toString();
return new ResourcePathTemplate(pathBuilder.toString(), pathParams);
}

public record ResourcePathTemplate(String resourcePathTemplate, List<ParameterResult> pathParams) {
}

/**
Expand Down
Loading

0 comments on commit 5b08bff

Please sign in to comment.