Skip to content

Commit a356eb8

Browse files
authored
refactor(api): extract common interface{} to int64 conversion utility (#539)
Add ParseInt64 utility function in pkg/api/params.go to eliminate duplicated type conversion logic across toolsets. This also fixes a bug in nodes.go where `case int:` was missing the assignment statement. Signed-off-by: Marc Nuri <[email protected]>
1 parent b48b6d7 commit a356eb8

File tree

6 files changed

+149
-35
lines changed

6 files changed

+149
-35
lines changed

pkg/api/params.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package api
2+
3+
import "fmt"
4+
5+
// ErrInvalidInt64Type is returned when a value cannot be converted to int64.
6+
type ErrInvalidInt64Type struct {
7+
Value interface{}
8+
}
9+
10+
func (e *ErrInvalidInt64Type) Error() string {
11+
return fmt.Sprintf("expected integer, got %T", e.Value)
12+
}
13+
14+
// ParseInt64 converts an interface{} value (typically from JSON-decoded tool arguments)
15+
// to int64. Handles float64 (JSON numbers), int, and int64 types.
16+
// Returns the converted value and nil error on success, or 0 and an ErrInvalidInt64Type if the type is unsupported.
17+
func ParseInt64(value interface{}) (int64, error) {
18+
switch v := value.(type) {
19+
case float64:
20+
return int64(v), nil
21+
case int:
22+
return int64(v), nil
23+
case int64:
24+
return v, nil
25+
default:
26+
return 0, &ErrInvalidInt64Type{Value: value}
27+
}
28+
}

pkg/api/params_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package api
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/suite"
7+
)
8+
9+
type ParamsSuite struct {
10+
suite.Suite
11+
}
12+
13+
func TestParamsSuite(t *testing.T) {
14+
suite.Run(t, new(ParamsSuite))
15+
}
16+
17+
func (s *ParamsSuite) TestParseInt64() {
18+
s.Run("float64 value is converted to int64", func() {
19+
result, err := ParseInt64(float64(42.0))
20+
s.NoError(err)
21+
s.Equal(int64(42), result)
22+
})
23+
24+
s.Run("float64 with decimal truncates to int64", func() {
25+
result, err := ParseInt64(float64(42.9))
26+
s.NoError(err)
27+
s.Equal(int64(42), result)
28+
})
29+
30+
s.Run("int value is converted to int64", func() {
31+
result, err := ParseInt64(int(100))
32+
s.NoError(err)
33+
s.Equal(int64(100), result)
34+
})
35+
36+
s.Run("int64 value is returned as-is", func() {
37+
result, err := ParseInt64(int64(999))
38+
s.NoError(err)
39+
s.Equal(int64(999), result)
40+
})
41+
42+
s.Run("negative float64 value is converted correctly", func() {
43+
result, err := ParseInt64(float64(-10.0))
44+
s.NoError(err)
45+
s.Equal(int64(-10), result)
46+
})
47+
48+
s.Run("negative int value is converted correctly", func() {
49+
result, err := ParseInt64(int(-5))
50+
s.NoError(err)
51+
s.Equal(int64(-5), result)
52+
})
53+
54+
s.Run("zero value is handled correctly", func() {
55+
result, err := ParseInt64(float64(0))
56+
s.NoError(err)
57+
s.Equal(int64(0), result)
58+
})
59+
60+
s.Run("string value returns error", func() {
61+
result, err := ParseInt64("not a number")
62+
s.Error(err)
63+
s.Equal(int64(0), result)
64+
s.Contains(err.Error(), "string")
65+
})
66+
67+
s.Run("nil value returns error", func() {
68+
result, err := ParseInt64(nil)
69+
s.Error(err)
70+
s.Equal(int64(0), result)
71+
})
72+
73+
s.Run("bool value returns error", func() {
74+
result, err := ParseInt64(true)
75+
s.Error(err)
76+
s.Equal(int64(0), result)
77+
s.Contains(err.Error(), "bool")
78+
})
79+
80+
s.Run("slice value returns error", func() {
81+
result, err := ParseInt64([]int{1, 2, 3})
82+
s.Error(err)
83+
s.Equal(int64(0), result)
84+
})
85+
86+
s.Run("map value returns error", func() {
87+
result, err := ParseInt64(map[string]int{"a": 1})
88+
s.Error(err)
89+
s.Equal(int64(0), result)
90+
})
91+
}
92+
93+
func (s *ParamsSuite) TestErrInvalidInt64Type() {
94+
s.Run("error includes type information", func() {
95+
err := &ErrInvalidInt64Type{Value: "test"}
96+
s.Contains(err.Error(), "string")
97+
})
98+
99+
s.Run("error can be type asserted", func() {
100+
_, err := ParseInt64("invalid")
101+
var typeErr *ErrInvalidInt64Type
102+
s.ErrorAs(err, &typeErr)
103+
s.Equal("invalid", typeErr.Value)
104+
})
105+
}

pkg/toolsets/core/nodes.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,10 @@ func nodesLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
108108
tailLines := params.GetArguments()["tailLines"]
109109
var tailInt int64
110110
if tailLines != nil {
111-
// Convert to int64 - safely handle both float64 (JSON number) and int types
112-
switch v := tailLines.(type) {
113-
case float64:
114-
tailInt = int64(v)
115-
case int:
116-
case int64:
117-
tailInt = v
118-
default:
119-
return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: expected integer, got %T", tailLines)), nil
111+
var err error
112+
tailInt, err = api.ParseInt64(tailLines)
113+
if err != nil {
114+
return api.NewToolCallResult("", fmt.Errorf("failed to parse tailLines parameter: %w", err)), nil
120115
}
121116
}
122117
ret, err := params.NodesLog(params, name, query, tailInt)

pkg/toolsets/core/pods.go

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -397,16 +397,10 @@ func podsLog(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
397397
tail := params.GetArguments()["tail"]
398398
var tailInt int64
399399
if tail != nil {
400-
// Convert to int64 - safely handle both float64 (JSON number) and int types
401-
switch v := tail.(type) {
402-
case float64:
403-
tailInt = int64(v)
404-
case int:
405-
tailInt = int64(v)
406-
case int64:
407-
tailInt = v
408-
default:
409-
return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: expected integer, got %T", tail)), nil
400+
var err error
401+
tailInt, err = api.ParseInt64(tail)
402+
if err != nil {
403+
return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: %w", err)), nil
410404
}
411405
}
412406

pkg/toolsets/core/resources.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -346,16 +346,11 @@ func resourcesScale(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
346346
}
347347

348348
func parseScaleValue(desiredScale interface{}) (int64, error) {
349-
switch s := desiredScale.(type) {
350-
case float64:
351-
return int64(s), nil
352-
case int:
353-
return int64(s), nil
354-
case int64:
355-
return s, nil
356-
default:
357-
return 0, fmt.Errorf("failed to parse scale parameter: expected integer, got %T", desiredScale)
349+
v, err := api.ParseInt64(desiredScale)
350+
if err != nil {
351+
return 0, fmt.Errorf("failed to parse scale parameter: %w", err)
358352
}
353+
return v, nil
359354
}
360355

361356
func parseGroupVersionKind(arguments map[string]interface{}) (*schema.GroupVersionKind, error) {

pkg/toolsets/kiali/logs.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,11 @@ func workloadLogsHandler(params api.ToolHandlerParams) (*api.ToolCallResult, err
8686

8787
// Convert tail to maxLines
8888
if tail != nil {
89-
switch v := tail.(type) {
90-
case float64:
91-
maxLines = fmt.Sprintf("%.0f", v)
92-
case int:
93-
maxLines = fmt.Sprintf("%d", v)
94-
case int64:
95-
maxLines = fmt.Sprintf("%d", v)
89+
tailInt, err := api.ParseInt64(tail)
90+
if err != nil {
91+
return api.NewToolCallResult("", fmt.Errorf("failed to parse tail parameter: %w", err)), nil
9692
}
93+
maxLines = fmt.Sprintf("%d", tailInt)
9794
}
9895

9996
// If no container specified, we need to get workload details first to find the main app container

0 commit comments

Comments
 (0)