This is an automated email from the ASF dual-hosted git repository.
abh1sar pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-go.git
The following commit(s) were added to refs/heads/main by this push:
new e463cd1 Fix list response keys derived from the API name (#162)
e463cd1 is described below
commit e463cd1c0db2582865a310ad8053653b74ce1f22
Author: Abhisar Sinha <[email protected]>
AuthorDate: Wed Aug 12 11:14:16 2026 +0530
Fix list response keys derived from the API name (#162)
CloudStack keys a list response's items by the response object name, not by
the
API name the generator derived the tag from, so 16 list APIs decoded as a
permanently empty slice while count parsed from the same body and no error
was
returned. The keys are now data in a documented listResponseKeys map rather
than
24 hand-written switch arms, each one observed on a real server and pinned
by a
test, leaving only genuine shape differences in the switch;
listCaCertificate and
listUsageServerMetrics also become pointers, since they return a single
object
with no count. Also moves the getRawValue empty-array guard from #154 into
the
generator, where it had been edited into the generated file only and so was
reverted by every `make code`.
---
cloudstack/ASNumberRangeService.go | 2 +-
cloudstack/BackupService.go | 2 +-
cloudstack/CertificateService.go | 3 +-
cloudstack/ClusterService.go | 2 +-
cloudstack/ExtensionService.go | 2 +-
cloudstack/FirewallService.go | 2 +-
cloudstack/HostService.go | 2 +-
cloudstack/NetworkService.go | 4 +-
cloudstack/SystemVMService.go | 2 +-
cloudstack/UsageService.go | 5 +-
cloudstack/UserService.go | 2 +-
cloudstack/VolumeService.go | 2 +-
cloudstack/ZoneService.go | 4 +-
generate/generate.go | 145 ++++++++++-----------
test/ListResponseJSONTagsRegression_test.go | 190 ++++++++++++++++++++++++++++
15 files changed, 273 insertions(+), 96 deletions(-)
diff --git a/cloudstack/ASNumberRangeService.go
b/cloudstack/ASNumberRangeService.go
index e781764..95f73c6 100644
--- a/cloudstack/ASNumberRangeService.go
+++ b/cloudstack/ASNumberRangeService.go
@@ -385,7 +385,7 @@ func (s *ASNumberRangeService) ListASNRanges(p
*ListASNRangesParams) (*ListASNRa
type ListASNRangesResponse struct {
Count int `json:"count"`
- ASNRanges []*ASNRange `json:"asnrange"`
+ ASNRanges []*ASNRange `json:"asnumberrange"`
}
type ASNRange struct {
diff --git a/cloudstack/BackupService.go b/cloudstack/BackupService.go
index 35f1f48..0abbe71 100644
--- a/cloudstack/BackupService.go
+++ b/cloudstack/BackupService.go
@@ -3544,7 +3544,7 @@ func (s *BackupService) ListBackupProviders(p
*ListBackupProvidersParams) (*List
type ListBackupProvidersResponse struct {
Count int `json:"count"`
- BackupProviders []*BackupProvider `json:"backupprovider"`
+ BackupProviders []*BackupProvider `json:"providers"`
}
type BackupProvider struct {
diff --git a/cloudstack/CertificateService.go b/cloudstack/CertificateService.go
index 26c5843..8939c35 100644
--- a/cloudstack/CertificateService.go
+++ b/cloudstack/CertificateService.go
@@ -366,8 +366,7 @@ func (s *CertificateService) ListCaCertificate(p
*ListCaCertificateParams) (*Lis
}
type ListCaCertificateResponse struct {
- Count int `json:"count"`
- CaCertificate []*CaCertificate `json:"cacertificate"`
+ CaCertificate *CaCertificate `json:"cacertificates"`
}
type CaCertificate struct {
diff --git a/cloudstack/ClusterService.go b/cloudstack/ClusterService.go
index 1e10470..e16583d 100644
--- a/cloudstack/ClusterService.go
+++ b/cloudstack/ClusterService.go
@@ -2686,7 +2686,7 @@ func (s *ClusterService) ListClustersMetrics(p
*ListClustersMetricsParams) (*Lis
type ListClustersMetricsResponse struct {
Count int `json:"count"`
- ClustersMetrics []*ClustersMetric `json:"clustersmetric"`
+ ClustersMetrics []*ClustersMetric `json:"cluster"`
}
type ClustersMetric struct {
diff --git a/cloudstack/ExtensionService.go b/cloudstack/ExtensionService.go
index fe3b281..818b4c6 100644
--- a/cloudstack/ExtensionService.go
+++ b/cloudstack/ExtensionService.go
@@ -1197,7 +1197,7 @@ func (s *ExtensionService) ListCustomActions(p
*ListCustomActionsParams) (*ListC
type ListCustomActionsResponse struct {
Count int `json:"count"`
- CustomActions []*CustomAction `json:"customaction"`
+ CustomActions []*CustomAction `json:"extensioncustomaction"`
}
type CustomAction struct {
diff --git a/cloudstack/FirewallService.go b/cloudstack/FirewallService.go
index e6b78c0..d00d50b 100644
--- a/cloudstack/FirewallService.go
+++ b/cloudstack/FirewallService.go
@@ -4094,7 +4094,7 @@ func (s *FirewallService) ListRoutingFirewallRules(p
*ListRoutingFirewallRulesPa
type ListRoutingFirewallRulesResponse struct {
Count int `json:"count"`
- RoutingFirewallRules []*RoutingFirewallRule `json:"routingfirewallrule"`
+ RoutingFirewallRules []*RoutingFirewallRule `json:"firewallrule"`
}
type RoutingFirewallRule struct {
diff --git a/cloudstack/HostService.go b/cloudstack/HostService.go
index 792b2bc..7706952 100644
--- a/cloudstack/HostService.go
+++ b/cloudstack/HostService.go
@@ -4193,7 +4193,7 @@ func (s *HostService) ListHostsMetrics(p
*ListHostsMetricsParams) (*ListHostsMet
type ListHostsMetricsResponse struct {
Count int `json:"count"`
- HostsMetrics []*HostsMetric `json:"hostsmetric"`
+ HostsMetrics []*HostsMetric `json:"host"`
}
type HostsMetric struct {
diff --git a/cloudstack/NetworkService.go b/cloudstack/NetworkService.go
index 4bf894f..fe99e20 100644
--- a/cloudstack/NetworkService.go
+++ b/cloudstack/NetworkService.go
@@ -3775,7 +3775,7 @@ func (s *NetworkService) ListNetworkIsolationMethods(p
*ListNetworkIsolationMeth
type ListNetworkIsolationMethodsResponse struct {
Count int `json:"count"`
- NetworkIsolationMethods []*NetworkIsolationMethod
`json:"networkisolationmethod"`
+ NetworkIsolationMethods []*NetworkIsolationMethod
`json:"isolationmethod"`
}
type NetworkIsolationMethod struct {
@@ -6424,7 +6424,7 @@ func (s *NetworkService) ListSupportedNetworkServices(p
*ListSupportedNetworkSer
type ListSupportedNetworkServicesResponse struct {
Count int `json:"count"`
- SupportedNetworkServices []*SupportedNetworkService
`json:"supportednetworkservice"`
+ SupportedNetworkServices []*SupportedNetworkService
`json:"networkservice"`
}
type SupportedNetworkService struct {
diff --git a/cloudstack/SystemVMService.go b/cloudstack/SystemVMService.go
index c9e573b..2fe912f 100644
--- a/cloudstack/SystemVMService.go
+++ b/cloudstack/SystemVMService.go
@@ -1108,7 +1108,7 @@ func (s *SystemVMService) ListSystemVmsUsageHistory(p
*ListSystemVmsUsageHistory
type ListSystemVmsUsageHistoryResponse struct {
Count int `json:"count"`
- SystemVmsUsageHistory []*SystemVmsUsageHistory
`json:"systemvmsusagehistory"`
+ SystemVmsUsageHistory []*SystemVmsUsageHistory `json:"virtualmachine"`
}
type SystemVmsUsageHistory struct {
diff --git a/cloudstack/UsageService.go b/cloudstack/UsageService.go
index ef89ced..d7310a7 100644
--- a/cloudstack/UsageService.go
+++ b/cloudstack/UsageService.go
@@ -1086,7 +1086,7 @@ func (s *UsageService) ListTrafficTypeImplementors(p
*ListTrafficTypeImplementor
type ListTrafficTypeImplementorsResponse struct {
Count int `json:"count"`
- TrafficTypeImplementors []*TrafficTypeImplementor
`json:"traffictypeimplementor"`
+ TrafficTypeImplementors []*TrafficTypeImplementor
`json:"traffictypeimplementorresponse"`
}
type TrafficTypeImplementor struct {
@@ -2128,8 +2128,7 @@ func (s *UsageService) ListUsageServerMetrics(p
*ListUsageServerMetricsParams) (
}
type ListUsageServerMetricsResponse struct {
- Count int `json:"count"`
- UsageServerMetrics []*UsageServerMetric `json:"usageservermetric"`
+ UsageServerMetrics *UsageServerMetric `json:"usageMetrics"`
}
type UsageServerMetric struct {
diff --git a/cloudstack/UserService.go b/cloudstack/UserService.go
index 10fc217..7c09679 100644
--- a/cloudstack/UserService.go
+++ b/cloudstack/UserService.go
@@ -955,7 +955,7 @@ func (s *UserService)
ListUserTwoFactorAuthenticatorProviders(p *ListUserTwoFact
type ListUserTwoFactorAuthenticatorProvidersResponse struct {
Count int
`json:"count"`
- UserTwoFactorAuthenticatorProviders
[]*UserTwoFactorAuthenticatorProvider
`json:"usertwofactorauthenticatorprovider"`
+ UserTwoFactorAuthenticatorProviders
[]*UserTwoFactorAuthenticatorProvider `json:"providers"`
}
type UserTwoFactorAuthenticatorProvider struct {
diff --git a/cloudstack/VolumeService.go b/cloudstack/VolumeService.go
index 386c72e..0b7f985 100644
--- a/cloudstack/VolumeService.go
+++ b/cloudstack/VolumeService.go
@@ -4490,7 +4490,7 @@ func (s *VolumeService) ListVolumesMetrics(p
*ListVolumesMetricsParams) (*ListVo
type ListVolumesMetricsResponse struct {
Count int `json:"count"`
- VolumesMetrics []*VolumesMetric `json:"volumesmetric"`
+ VolumesMetrics []*VolumesMetric `json:"volume"`
}
type VolumesMetric struct {
diff --git a/cloudstack/ZoneService.go b/cloudstack/ZoneService.go
index a9f0329..40440f7 100644
--- a/cloudstack/ZoneService.go
+++ b/cloudstack/ZoneService.go
@@ -2113,7 +2113,7 @@ func (s *ZoneService) ListIpv4SubnetsForZone(p
*ListIpv4SubnetsForZoneParams) (*
type ListIpv4SubnetsForZoneResponse struct {
Count int `json:"count"`
- Ipv4SubnetsForZone []*Ipv4SubnetsForZone `json:"ipv4subnetsforzone"`
+ Ipv4SubnetsForZone []*Ipv4SubnetsForZone `json:"zoneipv4subnet"`
}
type Ipv4SubnetsForZone struct {
@@ -3076,7 +3076,7 @@ func (s *ZoneService) ListZonesMetrics(p
*ListZonesMetricsParams) (*ListZonesMet
type ListZonesMetricsResponse struct {
Count int `json:"count"`
- ZonesMetrics []*ZonesMetric `json:"zonesmetric"`
+ ZonesMetrics []*ZonesMetric `json:"zone"`
}
type ZonesMetric struct {
diff --git a/generate/generate.go b/generate/generate.go
index d1d0802..a0b24e4 100644
--- a/generate/generate.go
+++ b/generate/generate.go
@@ -179,6 +179,59 @@ var customResponseStructTypes = map[string]string{
"findHostsForMigration": "HostForMigration",
}
+// listResponseKeys records the JSON key CloudStack uses for the items of a
list
+// response, for every API where that key differs from the one derived from the
+// API name.
+var listResponseKeys = map[string]string{
+ "listAsyncJobs": "asyncjobs",
+ "listDomainChildren": "domain",
+ "listEgressFirewallRules": "firewallrule",
+ "listGuestNetworkIpv6Prefixes": "guestnetworkipv6prefix",
+ "listHostHAProviders": "haprovider",
+ "listHostHAResources": "hostha",
+ "listHypervisorCapabilities": "hypervisorCapabilities",
+ "listImageStoreObjects": "datastoreobject",
+ "listLBHealthCheckPolicies": "healthcheckpolicies",
+ "listLBStickinessPolicies": "stickinesspolicies",
+ "listManagementServersMetrics": "managementserver",
+ "listObjectStoragePools": "objectstore",
+ "listSecondaryStorageSelectors": "heuristics",
+ "listStoragePoolObjects": "datastoreobject",
+ "listStoragePoolsMetrics": "storagepool",
+ "listVirtualMachinesMetrics": "virtualmachine",
+ "listVirtualMachinesUsageHistory": "virtualmachine",
+ "listVmwareDcVms": "unmanagedinstance",
+ "listVolumesUsageHistory": "volume",
+ "quotaSummary": "summary",
+ "quotaTariffList": "quotatariff",
+ "registerTemplate": "template",
+ "listVnfAppliances": "virtualmachine",
+ "listVnfTemplates": "template",
+ "listBackupProviders": "providers",
+ "listClustersMetrics": "cluster",
+ "listCustomActions": "extensioncustomaction",
+ "listHostsMetrics": "host",
+ "listNetworkIsolationMethods": "isolationmethod",
+ "listRoutingFirewallRules": "firewallrule",
+ "listSupportedNetworkServices": "networkservice",
+ "listSystemVmsUsageHistory": "virtualmachine",
+ "listTrafficTypeImplementors":
"traffictypeimplementorresponse",
+ "listUserTwoFactorAuthenticatorProviders": "providers",
+ "listVolumesMetrics": "volume",
+ "listZonesMetrics": "zone",
+ "listASNRanges": "asnumberrange",
+ "listIpv4SubnetsForZone": "zoneipv4subnet",
+}
+
+// listResponseKey returns the JSON key for an API's list items, preferring an
+// observed key over the one derived from the API name.
+func listResponseKey(apiName, listName string) string {
+ if key, ok := listResponseKeys[apiName]; ok {
+ return key
+ }
+ return strings.ToLower(parseSingular(listName))
+}
+
// We prefill this one value to make sure it is not
// created twice, as this is also a top level type.
var typeNames = map[string]bool{"Nic": true}
@@ -726,6 +779,9 @@ func (as *allServices) GeneralCode() ([]byte, error) {
pn(" if err := json.Unmarshal(v, &resp); err
!= nil {")
pn(" return nil, err")
pn(" }")
+ pn(" if len(resp) == 0 {")
+ pn(" return nil, fmt.Errorf(\"Unable
to extract raw value: empty array for key %%q in:\\n\\n%%s\\n\\n\", k,
string(b))")
+ pn(" }")
pn(" return resp[0], nil")
pn(" }")
pn(" }")
@@ -2078,40 +2134,28 @@ func (s *service) generateResponseType(a *API) {
a.Name == "registerCniConfiguration" || a.Name ==
"quotaBalance" || a.Name == "quotaSummary" || a.Name == "quotaTariffList" {
pn("type %s struct {", tn)
- // This nasty check is for some specific response that do not
behave consistent
+ // Responses whose *shape* differs: a single object instead of
an array,
+ // no count, more than one collection, or a hand-written field
list.
+ // Responses that differ only in the item key are handled by
the default
+ // arm through listResponseKeys.
switch a.Name {
- case "listAsyncJobs":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"asyncjobs")
case "listCapabilities":
pn(" %s *%s `json:\"%s\"`", ln, parseSingular(ln),
"capability")
- case "listEgressFirewallRules":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"firewallrule")
+ case "listCaCertificate":
+ // Returns a single object under "cacertificates", with
no count.
+ pn(" %s *%s `json:\"%s\"`", ln, parseSingular(ln),
"cacertificates")
+ case "listUsageServerMetrics":
+ // Returns a single object under "usageMetrics", with
no count.
+ pn(" %s *%s `json:\"%s\"`", ln, parseSingular(ln),
"usageMetrics")
case "listLoadBalancerRuleInstances":
pn(" Count int `json:\"count\"`")
pn(" LBRuleVMIDIPs []*%s `json:\"%s\"`",
parseSingular(ln), "lbrulevmidip")
pn(" LoadBalancerRuleInstances []*VirtualMachine
`json:\"%s\"`", strings.ToLower(parseSingular(ln)))
- case "listVirtualMachinesMetrics":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"virtualmachine")
- case "listManagementServersMetrics":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"managementserver")
case "listDbMetrics":
pn(" %s %s `json:\"%s\"`", ln, parseSingular(ln),
"dbMetrics")
- case "registerTemplate":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"template")
- case "listDomainChildren":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"domain")
case "findHostsForMigration":
pn(" Count int `json:\"count\"`")
pn(" Host []*%s `json:\"%s\"`",
customResponseStructTypes[a.Name], "host")
- case "listVmwareDcVms":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"unmanagedinstance")
case "registerUserData":
pn(" Account string `json:\"account\"`")
pn(" Accountid string `json:\"accountid\"`")
@@ -2126,69 +2170,14 @@ func (s *service) generateResponseType(a *API) {
pn(" Userdata string `json:\"userdata\"`")
case "registerCniConfiguration":
pn(" CniConfiguration *UserData
`json:\"cniconfig\"`")
- case "listObjectStoragePools":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"objectstore")
- case "listStoragePoolObjects":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"datastoreobject")
- case "listImageStoreObjects":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"datastoreobject")
- case "listVolumesUsageHistory":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"volume")
- case "listHostHAProviders":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"haprovider")
- case "listSecondaryStorageSelectors":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"heuristics")
- case "listVirtualMachinesUsageHistory":
- pn(" Count int
`json:\"count\"`")
- pn(" VirtualMachinesUsageHistory
[]*VirtualMachinesUsageHistory `json:\"virtualmachine\"`")
- case "listHostHAResources":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"hostha")
case "listInfrastructure":
pn(" Count int `json:\"count\"`")
pn(" %s *%s `json:\"%s\"`", ln, parseSingular(ln),
"infrastructure")
- case "listStoragePoolsMetrics":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"storagepool")
- case "quotaTariffList":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"quotatariff")
case "quotaBalance":
pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"balance")
- case "quotaSummary":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"summary")
- case "listHypervisorCapabilities":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"hypervisorCapabilities")
- case "listGuestNetworkIpv6Prefixes":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"guestnetworkipv6prefix")
- case "listLBHealthCheckPolicies":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"healthcheckpolicies")
- case "listLBStickinessPolicies":
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"stickinesspolicies")
- case "listVnfTemplates":
- // ListVnfTemplatesCmd is an empty subclass of
ListTemplatesCmd, so the
- // server returns the items under "template", not
"vnftemplate".
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"template")
- case "listVnfAppliances":
- // ListVnfAppliancesCmd inherits execute() from
ListVMsCmd, so the server
- // returns the items under "virtualmachine", not
"vnfappliance".
- pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
"virtualmachine")
default:
pn(" Count int `json:\"count\"`")
- pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
strings.ToLower(parseSingular(ln)))
+ pn(" %s []*%s `json:\"%s\"`", ln, parseSingular(ln),
listResponseKey(a.Name, ln))
}
pn("}")
pn("")
diff --git a/test/ListResponseJSONTagsRegression_test.go
b/test/ListResponseJSONTagsRegression_test.go
index 1552328..575e6fc 100644
--- a/test/ListResponseJSONTagsRegression_test.go
+++ b/test/ListResponseJSONTagsRegression_test.go
@@ -71,3 +71,193 @@ func TestListResponseJSONTagsPopulateSlices(t *testing.T) {
}
})
}
+
+// These assertions exist because the key cannot be derived from the API name,
so
+// a regeneration can otherwise quietly reintroduce any of them.
+func TestListResponseKeysObservedFromServer(t *testing.T) {
+ sliceCases := []struct {
+ name string
+ key string
+ body string
+ decode func([]byte) (int, error)
+ }{
+ {
+ name: "ASNRanges", key: "asnumberrange",
+ body: `{"count":1,"asnumberrange":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListASNRangesResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.ASNRanges), err
+ },
+ },
+ {
+ name: "Ipv4SubnetsForZone", key: "zoneipv4subnet",
+ body: `{"count":1,"zoneipv4subnet":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListIpv4SubnetsForZoneResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.Ipv4SubnetsForZone), err
+ },
+ },
+ {
+ name: "BackupProviders", key: "providers",
+ body: `{"count":1,"providers":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListBackupProvidersResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.BackupProviders), err
+ },
+ },
+ {
+ name: "ClustersMetrics", key: "cluster",
+ body: `{"count":1,"cluster":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListClustersMetricsResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.ClustersMetrics), err
+ },
+ },
+ {
+ name: "CustomActions", key: "extensioncustomaction",
+ body: `{"count":1,"extensioncustomaction":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListCustomActionsResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.CustomActions), err
+ },
+ },
+ {
+ name: "HostsMetrics", key: "host",
+ body: `{"count":1,"host":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListHostsMetricsResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.HostsMetrics), err
+ },
+ },
+ {
+ name: "NetworkIsolationMethods", key: "isolationmethod",
+ body: `{"count":1,"isolationmethod":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r
cloudstack.ListNetworkIsolationMethodsResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.NetworkIsolationMethods), err
+ },
+ },
+ {
+ name: "RoutingFirewallRules", key: "firewallrule",
+ body: `{"count":1,"firewallrule":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r
cloudstack.ListRoutingFirewallRulesResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.RoutingFirewallRules), err
+ },
+ },
+ {
+ name: "SupportedNetworkServices", key: "networkservice",
+ body: `{"count":1,"networkservice":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r
cloudstack.ListSupportedNetworkServicesResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.SupportedNetworkServices), err
+ },
+ },
+ {
+ name: "SystemVmsUsageHistory", key: "virtualmachine",
+ body: `{"count":1,"virtualmachine":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r
cloudstack.ListSystemVmsUsageHistoryResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.SystemVmsUsageHistory), err
+ },
+ },
+ {
+ name: "TrafficTypeImplementors", key:
"traffictypeimplementorresponse",
+ body:
`{"count":1,"traffictypeimplementorresponse":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r
cloudstack.ListTrafficTypeImplementorsResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.TrafficTypeImplementors), err
+ },
+ },
+ {
+ name: "UserTwoFactorAuthenticatorProviders", key:
"providers",
+ body: `{"count":1,"providers":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r
cloudstack.ListUserTwoFactorAuthenticatorProvidersResponse
+ err := json.Unmarshal(b, &r)
+ return
len(r.UserTwoFactorAuthenticatorProviders), err
+ },
+ },
+ {
+ name: "VolumesMetrics", key: "volume",
+ body: `{"count":1,"volume":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListVolumesMetricsResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.VolumesMetrics), err
+ },
+ },
+ {
+ name: "ZonesMetrics", key: "zone",
+ body: `{"count":1,"zone":[{}]}`,
+ decode: func(b []byte) (int, error) {
+ var r cloudstack.ListZonesMetricsResponse
+ err := json.Unmarshal(b, &r)
+ return len(r.ZonesMetrics), err
+ },
+ },
+ }
+
+ for _, tc := range sliceCases {
+ t.Run(tc.name, func(t *testing.T) {
+ n, err := tc.decode([]byte(tc.body))
+ if err != nil {
+ t.Fatalf("decoding key %q: %v", tc.key, err)
+ }
+ if n != 1 {
+ t.Fatalf("expected 1 item under key %q, got %d
(nil slice means the json tag does not match the key the server sends)",
tc.key, n)
+ }
+ })
+ }
+
+ // These two return a single object and no count, so the field is a
pointer
+ // rather than a slice.
+ objectCases := []struct {
+ name string
+ key string
+ body string
+ decode func([]byte) (bool, error)
+ }{
+ {
+ name: "CaCertificate", key: "cacertificates",
+ body: `{"cacertificates":{}}`,
+ decode: func(b []byte) (bool, error) {
+ var r cloudstack.ListCaCertificateResponse
+ err := json.Unmarshal(b, &r)
+ return r.CaCertificate != nil, err
+ },
+ },
+ {
+ name: "UsageServerMetrics", key: "usageMetrics",
+ body: `{"usageMetrics":{}}`,
+ decode: func(b []byte) (bool, error) {
+ var r cloudstack.ListUsageServerMetricsResponse
+ err := json.Unmarshal(b, &r)
+ return r.UsageServerMetrics != nil, err
+ },
+ },
+ }
+
+ for _, tc := range objectCases {
+ t.Run(tc.name, func(t *testing.T) {
+ ok, err := tc.decode([]byte(tc.body))
+ if err != nil {
+ t.Fatalf("decoding key %q: %v", tc.key, err)
+ }
+ if !ok {
+ t.Fatalf("expected an object under key %q, got
nil", tc.key)
+ }
+ })
+ }
+}