This is an automated email from the ASF dual-hosted git repository.
borisstoyanov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-cloudmonkey.git
The following commit(s) were added to refs/heads/main by this push:
new 9a04d5b Use Related metadata for autocomplete API discovery (#214)
9a04d5b is described below
commit 9a04d5bea6ede074afe48cdfb2b9ccf398fa4d5d
Author: Dheeraj Bansal <[email protected]>
AuthorDate: Wed Sep 9 13:08:08 2026 +0530
Use Related metadata for autocomplete API discovery (#214)
* feat: use Related metadata for autocomplete API discovery
* test: add autocomplete API discovery coverage
* Use Related metadata only when the noun heuristics find nothing
Checking Related before the heuristics changed 47 existing completions
against the bundled API cache, many of them wrong: registerIso projectid
resolved to listProjectAccounts instead of listProjects, acquirePodIpAddress
podid to listZones instead of listPods, and executeWebhookDelivery webhookid
to listWebhookDeliveries instead of listWebhooks. The loose Related match
picks the first list API in the array, whatever its noun.
Move the Related lookup after the noun heuristics and run it only when they
found no API. That keeps all 142 new completions Related metadata adds and
leaves every existing completion untouched. Add a test pinning the ordering.
---------
Co-authored-by: Boris Stoyanov <[email protected]>
---
cli/completer.go | 16 ++++
cli/completer_test.go | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 222 insertions(+)
diff --git a/cli/completer.go b/cli/completer.go
index 55d8207..9547f5a 100644
--- a/cli/completer.go
+++ b/cli/completer.go
@@ -287,6 +287,22 @@ func findAutocompleteAPI(arg *config.APIArg, apiFound
*config.API, apiMap map[st
relatedNoun = relatedNoun[:len(relatedNoun)-1]
}
+ // Prefer the API's own Related metadata when the noun heuristics found
+ // nothing, so entity-reference args still get completions.
+ if autocompleteAPI == nil {
+ for _, relatedAPI := range arg.Related {
+ if !strings.HasPrefix(strings.ToLower(relatedAPI),
"list") {
+ continue
+ }
+ for _, listAPI := range apiMap["list"] {
+ if strings.EqualFold(listAPI.Name, relatedAPI) {
+ config.Debug("Autocomplete: API found
using Related metadata: ", listAPI.Name)
+ return listAPI
+ }
+ }
+ }
+ }
+
// Heuristic: find any list API that contains the arg name
if autocompleteAPI == nil {
config.Debug("Finding possible API that have: ", argName, "
related APIs: ", arg.Related)
diff --git a/cli/completer_test.go b/cli/completer_test.go
new file mode 100644
index 0000000..aea7836
--- /dev/null
+++ b/cli/completer_test.go
@@ -0,0 +1,206 @@
+package cli
+
+import (
+ "testing"
+
+ "github.com/apache/cloudstack-cloudmonkey/config"
+)
+
+func TestFindAutocompleteAPIRelatedNounMatch(t *testing.T) {
+ arg := &config.APIArg{
+ Name: "domainid=",
+ Related: []string{
+ "createDomain",
+ "listDomains",
+ "updateDomain",
+ },
+ }
+
+ apiFound := &config.API{
+ Name: "listVirtualMachines",
+ Verb: "list",
+ Noun: "virtualmachines",
+ }
+
+ apiMap := map[string][]*config.API{
+ "list": {
+ {
+ Name: "listDomains",
+ Noun: "domains",
+ },
+ },
+ }
+
+ result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+ if result == nil {
+ t.Fatal("expected API, got nil")
+ }
+
+ if result.Name != "listDomains" {
+ t.Fatalf("expected listDomains, got %s", result.Name)
+ }
+}
+
+func TestFindAutocompleteAPIRelatedFallback(t *testing.T) {
+ arg := &config.APIArg{
+ Name: "domainid=",
+ Related: []string{
+ "listDomainChildren",
+ },
+ }
+
+ apiFound := &config.API{
+ Name: "listVirtualMachines",
+ Verb: "list",
+ Noun: "virtualmachines",
+ }
+
+ apiMap := map[string][]*config.API{
+ "list": {
+ {
+ Name: "listDomainChildren",
+ Noun: "domainchildren",
+ },
+ },
+ }
+
+ result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+ if result == nil {
+ t.Fatal("expected API, got nil")
+ }
+
+ if result.Name != "listDomainChildren" {
+ t.Fatalf("expected listDomainChildren, got %s", result.Name)
+ }
+}
+
+func TestFindAutocompleteAPIEmptyRelatedFallsBackToHeuristic(t *testing.T) {
+ arg := &config.APIArg{
+ Name: "zoneid=",
+ }
+
+ apiFound := &config.API{
+ Name: "listVirtualMachines",
+ Verb: "list",
+ Noun: "virtualmachines",
+ }
+
+ apiMap := map[string][]*config.API{
+ "list": {
+ {
+ Name: "listZones",
+ Noun: "zones",
+ },
+ },
+ }
+
+ result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+ if result == nil {
+ t.Fatal("expected API, got nil")
+ }
+
+ if result.Name != "listZones" {
+ t.Fatalf("expected listZones, got %s", result.Name)
+ }
+}
+
+func TestFindAutocompleteAPINonListRelatedFallsBackToHeuristic(t *testing.T) {
+ arg := &config.APIArg{
+ Name: "zoneid=",
+ Related: []string{
+ "createZone",
+ "updateZone",
+ },
+ }
+
+ apiFound := &config.API{
+ Name: "listVirtualMachines",
+ Verb: "list",
+ Noun: "virtualmachines",
+ }
+
+ apiMap := map[string][]*config.API{
+ "list": {
+ {
+ Name: "listZones",
+ Noun: "zones",
+ },
+ },
+ }
+
+ result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+ if result == nil {
+ t.Fatal("expected API, got nil")
+ }
+
+ if result.Name != "listZones" {
+ t.Fatalf("expected listZones, got %s", result.Name)
+ }
+}
+
+func TestFindAutocompleteAPIMapTypeReturnsNil(t *testing.T) {
+ arg := &config.APIArg{
+ Type: "map",
+ }
+
+ apiFound := &config.API{
+ Name: "listVirtualMachines",
+ Verb: "list",
+ Noun: "virtualmachines",
+ }
+
+ apiMap := map[string][]*config.API{
+ "list": {},
+ }
+
+ result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+ if result != nil {
+ t.Fatalf("expected nil, got %v", result)
+ }
+}
+
+func TestFindAutocompleteAPIHeuristicWinsOverRelated(t *testing.T) {
+ // registerIso's projectid arg lists many related APIs; the noun
heuristic
+ // must keep winning so the completion stays listProjects.
+ arg := &config.APIArg{
+ Name: "projectid=",
+ Related: []string{
+ "listProjectAccounts",
+ "listProjects",
+ },
+ }
+
+ apiFound := &config.API{
+ Name: "registerIso",
+ Verb: "register",
+ Noun: "iso",
+ }
+
+ apiMap := map[string][]*config.API{
+ "list": {
+ {
+ Name: "listProjectAccounts",
+ Noun: "projectaccounts",
+ },
+ {
+ Name: "listProjects",
+ Noun: "projects",
+ },
+ },
+ }
+
+ result := findAutocompleteAPI(arg, apiFound, apiMap)
+
+ if result == nil {
+ t.Fatal("expected API, got nil")
+ }
+
+ if result.Name != "listProjects" {
+ t.Fatalf("expected listProjects, got %s", result.Name)
+ }
+}