Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show resource path params as custom properties in the flow diagram #531

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,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 @@ -120,6 +121,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 @@ -311,10 +313,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 @@ -325,17 +326,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);
LakshanWeerasinghe marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -451,39 +491,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
Loading