Skip to content

Commit 6eab126

Browse files
committed
feat(endpoints): add EndpointKnowledgeWebApiProvider for API call tree lookup #308
- Introduce EndpointKnowledgeWebApiProvider to handle API call tree lookup based on HTTP method and URL. - Refactor WebApiViewFunctionProvider to use collectUrls for endpoint collection. - Update KnowledgeFunctionProvider to support API method validation and lookup. - Add UrlCollector utility for collecting URL mappings.
1 parent 696d1a7 commit 6eab126

File tree

7 files changed

+162
-33
lines changed

7 files changed

+162
-33
lines changed

core/src/main/kotlin/cc/unitmesh/devti/bridge/knowledge/KnowledgeFunctionProvider.kt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
package cc.unitmesh.devti.bridge.knowledge
22

33
import cc.unitmesh.devti.bridge.KnowledgeTransfer
4+
import cc.unitmesh.devti.bridge.provider.KnowledgeWebApiProvider
45
import cc.unitmesh.devti.provider.toolchain.ToolchainFunctionProvider
56
import com.intellij.openapi.project.Project
67

8+
val API_METHODS: List<String> = listOf("GET", "POST", "PUT", "DELETE", "PATCH")
9+
10+
/**
11+
* ```devin
12+
* 从 API 调用链来进行分析
13+
* /knowledge:GET#/api/blog
14+
* 从 Controller 到 Repository 的调用链
15+
* /knowledge:BlogController#getBlogBySlug
16+
* ```
17+
*/
718
class KnowledgeFunctionProvider : ToolchainFunctionProvider {
819
override fun funcNames(): List<String> = listOf(KnowledgeTransfer.Knowledge.name)
920

1021
override fun isApplicable(project: Project, funcName: String): Boolean =
1122
funcName == KnowledgeTransfer.Knowledge.name
1223

24+
1325
/**
1426
* 1. try use KnowledgeWebApiProvider
1527
*
@@ -22,6 +34,23 @@ class KnowledgeFunctionProvider : ToolchainFunctionProvider {
2234
args: List<Any>,
2335
allVariables: Map<String, Any?>
2436
): Any {
37+
// split prop to method and path
38+
val split = prop.split("#")
39+
if (split.size != 2) {
40+
return "Invalid API format"
41+
}
42+
43+
val method = split[0]
44+
if (!API_METHODS.contains(method)) {
45+
return "Invalid API method"
46+
}
47+
48+
val path = split[1]
49+
50+
val elements = KnowledgeWebApiProvider.available(project).map {
51+
it.lookupApiCallTree(project, method, path)
52+
}.flatten()
53+
2554
return ""
2655
}
2756
}

core/src/main/kotlin/cc/unitmesh/devti/bridge/provider/KnowledgeWebApiProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ abstract class KnowledgeWebApiProvider : LazyExtensionInstance<KnowledgeWebApiPr
1414

1515
abstract fun isApplicable(project: Project): Boolean
1616

17-
abstract fun lookupKnowledgeTree(project: Project, httpMethod: String, httpUrl: String): List<PsiElement>
17+
abstract fun lookupApiCallTree(project: Project, httpMethod: String, httpUrl: String): List<PsiElement>
1818

1919
companion object {
2020
val EP_NAME: ExtensionPointName<KnowledgeWebApiProvider> =
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
从 API 调用链来进行分析
2-
/knowledge:GET#/api/blog
2+
/knowledge:GET#/api/blog/* [注:这里 * 代表 blog slug,等同于 SpringMVC 的 @PathVariable]
33
从 Controller 到 Repository 的调用链
44
/knowledge:BlogController#getBlogBySlug
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package cc.unitmesh.endpoints.bridge
2+
3+
import cc.unitmesh.devti.bridge.provider.KnowledgeWebApiProvider
4+
import com.intellij.microservices.endpoints.EndpointsProvider
5+
import com.intellij.openapi.application.runReadAction
6+
import com.intellij.openapi.progress.ProgressIndicator
7+
import com.intellij.openapi.progress.ProgressManager
8+
import com.intellij.openapi.progress.Task
9+
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator
10+
import com.intellij.openapi.project.Project
11+
import com.intellij.psi.PsiElement
12+
import com.intellij.spring.mvc.jam.RequestMethod
13+
import com.intellij.spring.mvc.mapping.UrlMappingElement
14+
import java.util.concurrent.CompletableFuture
15+
16+
class EndpointKnowledgeWebApiProvider : KnowledgeWebApiProvider() {
17+
override fun isApplicable(project: Project): Boolean = EndpointsProvider.hasAnyProviders()
18+
19+
override fun lookupApiCallTree(
20+
project: Project,
21+
httpMethod: String,
22+
httpUrl: String
23+
): List<PsiElement> {
24+
val endpointsProviderList = runReadAction { EndpointsProvider.getAvailableProviders(project).toList() }
25+
if (endpointsProviderList.isEmpty()) return emptyList()
26+
27+
val future = CompletableFuture<List<PsiElement>>()
28+
val task = object : Task.Backgroundable(project, "Processing context", false) {
29+
override fun run(indicator: ProgressIndicator) {
30+
future.complete(
31+
this@EndpointKnowledgeWebApiProvider.collectElements(
32+
project,
33+
endpointsProviderList,
34+
httpMethod,
35+
httpUrl
36+
)
37+
)
38+
}
39+
}
40+
41+
ProgressManager.getInstance()
42+
.runProcessWithProgressAsynchronously(task, BackgroundableProcessIndicator(task))
43+
44+
return future.get()
45+
}
46+
47+
private fun collectElements(
48+
project: Project,
49+
model: List<EndpointsProvider<*, *>>,
50+
httpMethod: String,
51+
httpUrl: String
52+
): List<PsiElement> = runReadAction {
53+
val collectUrls = collectUrls(project, model)
54+
val requestMethod: RequestMethod = httpMethod.toRequestMethod()
55+
val navElement = collectUrls
56+
.filter {
57+
it.method.contains(requestMethod) && compareUrl(it, httpUrl)
58+
}
59+
.mapNotNull { it.navigationTarget }
60+
.distinct()
61+
62+
return@runReadAction navElement
63+
}
64+
65+
private fun compareUrl(element: UrlMappingElement, httpUrl: String): Boolean {
66+
val queriedRequestUrl = httpUrl.trimStart('/')
67+
val projectUrls = element.urlPath.toStringWithStars().trimStart('/')
68+
return projectUrls == queriedRequestUrl
69+
}
70+
}
71+
72+
private fun String.toRequestMethod(): RequestMethod {
73+
return when (this) {
74+
"GET" -> RequestMethod.GET
75+
"POST" -> RequestMethod.POST
76+
"PUT" -> RequestMethod.PUT
77+
"DELETE" -> RequestMethod.DELETE
78+
"PATCH" -> RequestMethod.PATCH
79+
"HEAD" -> RequestMethod.HEAD
80+
"OPTIONS" -> RequestMethod.OPTIONS
81+
else -> RequestMethod.GET
82+
}
83+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cc.unitmesh.endpoints.bridge
2+
3+
import com.intellij.microservices.endpoints.EndpointsProvider
4+
import com.intellij.microservices.endpoints.EndpointsUrlTargetProvider
5+
import com.intellij.microservices.endpoints.ModuleEndpointsFilter
6+
import com.intellij.openapi.application.runReadAction
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.project.modules
9+
import com.intellij.spring.model.SpringBeanPointer
10+
import com.intellij.spring.mvc.mapping.UrlMappingElement
11+
12+
fun collectUrls(project: Project, model: List<EndpointsProvider<*, *>>): List<UrlMappingElement> = runReadAction {
13+
val availableProviders = model
14+
.filter { it.getStatus(project) == EndpointsProvider.Status.HAS_ENDPOINTS }
15+
.filterIsInstance<EndpointsUrlTargetProvider<SpringBeanPointer<*>, UrlMappingElement>>()
16+
17+
val modules = project.modules
18+
val groups = modules.map { module ->
19+
val moduleEndpointsFilter = ModuleEndpointsFilter(module, false, false)
20+
availableProviders.map { provider ->
21+
provider.getEndpointGroups(project, moduleEndpointsFilter)
22+
}.flatten()
23+
}.flatten()
24+
25+
val map: List<UrlMappingElement> = groups.map { group ->
26+
availableProviders.map {
27+
it.getEndpoints(group)
28+
}.flatten()
29+
}.flatten()
30+
31+
return@runReadAction map
32+
}

exts/ext-endpoints/src/233/main/kotlin/cc/unitmesh/endpoints/bridge/WebApiViewFunctionProvider.kt

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@ package cc.unitmesh.endpoints.bridge
33
import cc.unitmesh.devti.bridge.ArchViewCommand
44
import cc.unitmesh.devti.provider.toolchain.ToolchainFunctionProvider
55
import com.intellij.microservices.endpoints.EndpointsProvider
6-
import com.intellij.microservices.endpoints.EndpointsUrlTargetProvider
7-
import com.intellij.microservices.endpoints.ModuleEndpointsFilter
86
import com.intellij.openapi.application.runReadAction
97
import com.intellij.openapi.progress.ProgressIndicator
108
import com.intellij.openapi.progress.ProgressManager
119
import com.intellij.openapi.progress.Task
1210
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator
1311
import com.intellij.openapi.project.Project
14-
import com.intellij.openapi.project.modules
15-
import com.intellij.spring.model.SpringBeanPointer
1612
import com.intellij.spring.mvc.mapping.UrlMappingElement
1713
import java.util.concurrent.CompletableFuture
1814

@@ -33,7 +29,19 @@ class WebApiViewFunctionProvider : ToolchainFunctionProvider {
3329
val future = CompletableFuture<String>()
3430
val task = object : Task.Backgroundable(project, "Processing context", false) {
3531
override fun run(indicator: ProgressIndicator) {
36-
future.complete(this@WebApiViewFunctionProvider.collectApis(project, endpointsProviderList))
32+
val map = collectUrls(project, endpointsProviderList)
33+
val result =
34+
"""Here is current project web api endpoints, ${map.size}:""" + map.joinToString("\n") { url ->
35+
url.method.joinToString("\n") {
36+
"$it - ${url.urlPath.toStringWithStars()}" + " (${
37+
UrlMappingElement.getContainingFileName(
38+
url
39+
)
40+
})"
41+
}
42+
}
43+
44+
future.complete(result)
3745
}
3846
}
3947

@@ -42,30 +50,5 @@ class WebApiViewFunctionProvider : ToolchainFunctionProvider {
4250

4351
return future.get()
4452
}
45-
46-
private fun collectApis(project: Project, model: List<EndpointsProvider<*, *>>): String = runReadAction {
47-
val availableProviders = model
48-
.filter { it.getStatus(project) == EndpointsProvider.Status.HAS_ENDPOINTS }
49-
.filterIsInstance<EndpointsUrlTargetProvider<SpringBeanPointer<*>, UrlMappingElement>>()
50-
51-
val modules = project.modules
52-
val groups = modules.map { module ->
53-
val moduleEndpointsFilter = ModuleEndpointsFilter(module, false, false)
54-
availableProviders.map { provider ->
55-
provider.getEndpointGroups(project, moduleEndpointsFilter)
56-
}.flatten()
57-
}.flatten()
58-
59-
val map: List<UrlMappingElement> = groups.map { group ->
60-
availableProviders.map {
61-
it.getEndpoints(group)
62-
}.flatten()
63-
}.flatten()
64-
65-
"""Here is current project web api endpoints, ${map.size}:""" + map.joinToString("\n") { url ->
66-
url.method.joinToString("\n") {
67-
"$it - ${url.urlPath.toStringWithStars()}" + " (${UrlMappingElement.getContainingFileName(url)})"
68-
}
69-
}
70-
}
7153
}
54+

exts/ext-endpoints/src/233/main/resources/cc.unitmesh.endpoints.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@
1010
<!-- Since it's very slow to load all the endpoints, we disable it by default. -->
1111
<!-- <chatContextProvider implementation="cc.unitmesh.endpoints.provider.EndpointsContextProvider"/>-->
1212
<toolchainFunctionProvider implementation="cc.unitmesh.endpoints.bridge.WebApiViewFunctionProvider"/>
13+
14+
<knowledgeWebApiProvide implementation="cc.unitmesh.endpoints.bridge.EndpointKnowledgeWebApiProvider"/>
1315
</extensions>
1416
</idea-plugin>

0 commit comments

Comments
 (0)