From a4b6f20d638b6d423e88f377780492155445d33d Mon Sep 17 00:00:00 2001 From: Pavel Kondratyev Date: Tue, 20 May 2025 16:34:46 +0300 Subject: [PATCH 1/5] add large storage support --- proxmox.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/proxmox.go b/proxmox.go index b38fffb..3309d19 100644 --- a/proxmox.go +++ b/proxmox.go @@ -324,6 +324,68 @@ func (c *Client) handleResponse(res *http.Response, v interface{}) error { } if body, ok := datakey["data"]; ok { + if storagesPtr, ok := v.(*Storages); ok { + var rawList []map[string]interface{} + + decoder := json.NewDecoder(bytes.NewReader(body)) + decoder.UseNumber() + if err := decoder.Decode(&rawList); err != nil { + return err + } + + var result Storages + + for _, raw := range rawList { + s := &Storage{} + + if name, ok := raw["storage"].(string); ok { + s.Name = name + } + if content, ok := raw["content"].(string); ok { + s.Content = content + } + if typ, ok := raw["type"].(string); ok { + s.Type = typ + } + if enabled, ok := raw["enabled"].(json.Number); ok { + if i, err := enabled.Int64(); err == nil { + s.Enabled = int(i) + } + } + if active, ok := raw["active"].(json.Number); ok { + if i, err := active.Int64(); err == nil { + s.Active = int(i) + } + } + if shared, ok := raw["shared"].(json.Number); ok { + if i, err := shared.Int64(); err == nil { + s.Shared = int(i) + } + } + if usedFraction, ok := raw["used_fraction"].(json.Number); ok { + if f, err := usedFraction.Float64(); err == nil { + s.UsedFraction = f + } + } + + for key, dest := range map[string]*uint64{ + "avail": &s.Avail, + "used": &s.Used, + "total": &s.Total, + } { + if n, ok := raw[key].(json.Number); ok { + if f, err := n.Float64(); err == nil { + *dest = uint64(f) + } + } + } + + result = append(result, s) + } + + *storagesPtr = result + return nil + } return json.Unmarshal(body, &v) } From 686d045db5ed91ff02ce531191465e4226c4b605 Mon Sep 17 00:00:00 2001 From: Pavel Kondratyev Date: Tue, 3 Jun 2025 16:52:31 +0300 Subject: [PATCH 2/5] Add generic for handle number type assertion --- proxmox.go | 58 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/proxmox.go b/proxmox.go index 3309d19..9e810b0 100644 --- a/proxmox.go +++ b/proxmox.go @@ -280,6 +280,24 @@ func (c *Client) authHeaders(header *http.Header) { } } +func extractJSONNumber[T int64 | float64](raw map[string]interface{}, key string, assign func(T)) { + if num, ok := raw[key].(json.Number); ok { + var val any + var err error + + switch any(*new(T)).(type) { + case int64: + val, err = num.Int64() + case float64: + val, err = num.Float64() + } + + if err == nil { + assign(val.(T)) + } + } +} + func (c *Client) handleResponse(res *http.Response, v interface{}) error { if res.StatusCode == http.StatusInternalServerError || res.StatusCode == http.StatusNotImplemented { @@ -347,37 +365,27 @@ func (c *Client) handleResponse(res *http.Response, v interface{}) error { if typ, ok := raw["type"].(string); ok { s.Type = typ } - if enabled, ok := raw["enabled"].(json.Number); ok { - if i, err := enabled.Int64(); err == nil { - s.Enabled = int(i) - } - } - if active, ok := raw["active"].(json.Number); ok { - if i, err := active.Int64(); err == nil { - s.Active = int(i) - } - } - if shared, ok := raw["shared"].(json.Number); ok { - if i, err := shared.Int64(); err == nil { - s.Shared = int(i) - } - } - if usedFraction, ok := raw["used_fraction"].(json.Number); ok { - if f, err := usedFraction.Float64(); err == nil { - s.UsedFraction = f - } - } + extractJSONNumber(raw, "enabled", func(v int64) { + s.Enabled = int(v) + }) + extractJSONNumber(raw, "active", func(v int64) { + s.Active = int(v) + }) + extractJSONNumber(raw, "shared", func(v int64) { + s.Shared = int(v) + }) + extractJSONNumber(raw, "used_fraction", func(v float64) { + s.UsedFraction = v + }) for key, dest := range map[string]*uint64{ "avail": &s.Avail, "used": &s.Used, "total": &s.Total, } { - if n, ok := raw[key].(json.Number); ok { - if f, err := n.Float64(); err == nil { - *dest = uint64(f) - } - } + extractJSONNumber(raw, key, func(v float64) { + *dest = uint64(v) + }) } result = append(result, s) From 7ca4630df8540dfd2f469feb6955340edfa87b5f Mon Sep 17 00:00:00 2001 From: Nikolay Deryugin <96112649+fireflg@users.noreply.github.com> Date: Wed, 11 Jun 2025 17:39:49 +0300 Subject: [PATCH 3/5] Add test for float value --- proxmox_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/proxmox_test.go b/proxmox_test.go index a1fcbfc..42ffefb 100644 --- a/proxmox_test.go +++ b/proxmox_test.go @@ -159,4 +159,31 @@ func TestClient_handleResponse(t *testing.T) { err = client.handleResponse(resp, &testData) assert.NotNil(t, err) assert.Equal(t, "bad request: - {\"test\":\"data\"}", err.Error()) + + // storage total is float + storageData := `{ + "data": [ + { + "storage": "local", + "enabled": 1, + "active": 1, + "total": 1.12589990684262e+15 + } + ] + }` + resp = &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(storageData)), + } + + var storages Storages + err = client.handleResponse(resp, &storages) + storage := storages[0] + fmt.Println(storage) + assert.Equal(t, "local", storage.Name) + assert.Equal(t, 1, storage.Enabled) + assert.Equal(t, 1, storage.Active) + + expectedTotal := uint64(1125899906842620) + assert.Equal(t, expectedTotal, storage.Total) } From 3596347474ac0b974b561e76c7e1eb8432cd3d93 Mon Sep 17 00:00:00 2001 From: Nikolay Deryugin <96112649+fireflg@users.noreply.github.com> Date: Wed, 11 Jun 2025 18:27:53 +0300 Subject: [PATCH 4/5] Add int test --- proxmox_test.go | 62 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/proxmox_test.go b/proxmox_test.go index 42ffefb..91cea44 100644 --- a/proxmox_test.go +++ b/proxmox_test.go @@ -159,31 +159,63 @@ func TestClient_handleResponse(t *testing.T) { err = client.handleResponse(resp, &testData) assert.NotNil(t, err) assert.Equal(t, "bad request: - {\"test\":\"data\"}", err.Error()) - - // storage total is float + + // storage test with float total storageData := `{ - "data": [ - { - "storage": "local", - "enabled": 1, - "active": 1, - "total": 1.12589990684262e+15 - } - ] - }` + "data": [ + { + "storage": "local", + "enabled": 1, + "active": 1, + "total": 1.12589990684262e+15 + } + ] + }` resp = &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(storageData)), } - var storages Storages - err = client.handleResponse(resp, &storages) - storage := storages[0] - fmt.Println(storage) + var testStorages Storages + err = client.handleResponse(resp, &testStorages) + require.NoError(t, err) + + require.Len(t, testStorages, 1) + storage := testStorages[0] + assert.Equal(t, "local", storage.Name) assert.Equal(t, 1, storage.Enabled) assert.Equal(t, 1, storage.Active) expectedTotal := uint64(1125899906842620) assert.Equal(t, expectedTotal, storage.Total) + + // storage test with int total + storageData = `{ + "data": [ + { + "storage": "local", + "enabled": 1, + "active": 1, + "total": 150 + } + ] + }` + resp = &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader(storageData)), + } + + err = client.handleResponse(resp, &testStorages) + require.NoError(t, err) + + require.Len(t, testStorages, 1) + storage = testStorages[0] + + assert.Equal(t, "local", storage.Name) + assert.Equal(t, 1, storage.Enabled) + assert.Equal(t, 1, storage.Active) + + expectedTotal = uint64(150) + assert.Equal(t, expectedTotal, storage.Total) } From d8528eac11fd5bdb27eaca1a3af8fbaf4af391a8 Mon Sep 17 00:00:00 2001 From: Deryugin Nikolay Nikolaevich Date: Wed, 11 Jun 2025 18:58:03 +0300 Subject: [PATCH 5/5] Add method for storage --- proxmox.go | 51 +++++++++++++++++++++++++++++++++++++++++++++---- proxmox_test.go | 49 ++++++++++++++++++++++++----------------------- 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/proxmox.go b/proxmox.go index 9e810b0..e7a5770 100644 --- a/proxmox.go +++ b/proxmox.go @@ -342,7 +342,8 @@ func (c *Client) handleResponse(res *http.Response, v interface{}) error { } if body, ok := datakey["data"]; ok { - if storagesPtr, ok := v.(*Storages); ok { + switch typedV := v.(type) { + case *Storages: var rawList []map[string]interface{} decoder := json.NewDecoder(bytes.NewReader(body)) @@ -350,7 +351,6 @@ func (c *Client) handleResponse(res *http.Response, v interface{}) error { if err := decoder.Decode(&rawList); err != nil { return err } - var result Storages for _, raw := range rawList { @@ -391,10 +391,53 @@ func (c *Client) handleResponse(res *http.Response, v interface{}) error { result = append(result, s) } - *storagesPtr = result + *typedV = result + return nil + case *Storage: + var raw map[string]interface{} + + decoder := json.NewDecoder(bytes.NewReader(body)) + decoder.UseNumber() + if err := decoder.Decode(&raw); err != nil { + return err + } + s := typedV + + if name, ok := raw["storage"].(string); ok { + s.Name = name + } + if content, ok := raw["content"].(string); ok { + s.Content = content + } + if typ, ok := raw["type"].(string); ok { + s.Type = typ + } + extractJSONNumber(raw, "enabled", func(v int64) { + s.Enabled = int(v) + }) + extractJSONNumber(raw, "active", func(v int64) { + s.Active = int(v) + }) + extractJSONNumber(raw, "shared", func(v int64) { + s.Shared = int(v) + }) + extractJSONNumber(raw, "used_fraction", func(v float64) { + s.UsedFraction = v + }) + + for key, dest := range map[string]*uint64{ + "avail": &s.Avail, + "used": &s.Used, + "total": &s.Total, + } { + extractJSONNumber(raw, key, func(v float64) { + *dest = uint64(v) + }) + } return nil + default: + return json.Unmarshal(body, &v) } - return json.Unmarshal(body, &v) } return json.Unmarshal(body, &v) // assume passed in type fully supports response diff --git a/proxmox_test.go b/proxmox_test.go index 91cea44..517916c 100644 --- a/proxmox_test.go +++ b/proxmox_test.go @@ -7,6 +7,8 @@ import ( "strings" "testing" + "github.com/stretchr/testify/require" + "github.com/luthermonson/go-proxmox/tests/mocks" "github.com/luthermonson/go-proxmox/tests/mocks/config" "github.com/stretchr/testify/assert" @@ -160,27 +162,33 @@ func TestClient_handleResponse(t *testing.T) { assert.NotNil(t, err) assert.Equal(t, "bad request: - {\"test\":\"data\"}", err.Error()) - // storage test with float total - storageData := `{ + // storages test with float total + storagesData := `{ "data": [ { "storage": "local", "enabled": 1, "active": 1, "total": 1.12589990684262e+15 + }, + { + "storage": "local2", + "enabled": 1, + "active": 1, + "total": 1.12589990684262e+15 } ] }` resp = &http.Response{ StatusCode: http.StatusOK, - Body: io.NopCloser(strings.NewReader(storageData)), + Body: io.NopCloser(strings.NewReader(storagesData)), } var testStorages Storages err = client.handleResponse(resp, &testStorages) require.NoError(t, err) - require.Len(t, testStorages, 1) + require.Len(t, testStorages, 2) storage := testStorages[0] assert.Equal(t, "local", storage.Name) @@ -190,32 +198,25 @@ func TestClient_handleResponse(t *testing.T) { expectedTotal := uint64(1125899906842620) assert.Equal(t, expectedTotal, storage.Total) - // storage test with int total - storageData = `{ - "data": [ - { - "storage": "local", - "enabled": 1, - "active": 1, - "total": 150 + // storage test with float total + storageData := `{ + "data": { + "storage": "local3", + "enabled": 4, + "active": 5, + "total": 1.12589990684262e+15 } - ] }` resp = &http.Response{ StatusCode: http.StatusOK, Body: io.NopCloser(strings.NewReader(storageData)), } - - err = client.handleResponse(resp, &testStorages) + var testStorage Storage + err = client.handleResponse(resp, &testStorage) require.NoError(t, err) - require.Len(t, testStorages, 1) - storage = testStorages[0] - - assert.Equal(t, "local", storage.Name) - assert.Equal(t, 1, storage.Enabled) - assert.Equal(t, 1, storage.Active) - - expectedTotal = uint64(150) - assert.Equal(t, expectedTotal, storage.Total) + assert.Equal(t, "local3", testStorage.Name) + assert.Equal(t, 4, testStorage.Enabled) + assert.Equal(t, 5, testStorage.Active) + assert.Equal(t, expectedTotal, testStorage.Total) }