Skip to content

Commit 9c8cc6e

Browse files
cvclaude
andcommitted
chore: Enhance golangci-lint config with additional linters
Add new linters: - bodyclose: HTTP response body closure - durationcheck: Duration multiplication bugs - errorlint: Error wrapping issues (with fixes) - gosec: Security issues (with sensible exclusions) - goconst: Repeated strings - gocritic: Style improvements (with fixes) - usestdlibvars: Use stdlib constants (with fixes) - noctx: Context usage Fixes applied: - Use http.MethodPost/MethodGet instead of string literals - Use errors.As/errors.Is for wrapped error handling - Convert if-else chains to switch statements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d865015 commit 9c8cc6e

File tree

8 files changed

+73
-22
lines changed

8 files changed

+73
-22
lines changed

.golangci.yml

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,46 @@ version: "2"
22

33
linters:
44
enable:
5-
- testifylint
5+
# Bug finders
6+
- bodyclose # HTTP response body closure
7+
- durationcheck # Duration multiplication bugs
8+
- errorlint # Error wrapping issues
9+
- noctx # Missing context.Context usage
10+
11+
# Security
12+
- gosec # Security issues
13+
14+
# Code quality
15+
- goconst # Repeated strings that could be constants
16+
- gocritic # Bugs, performance, style issues
17+
- misspell # Typos
18+
- unconvert # Unnecessary type conversions
19+
- usestdlibvars # Use stdlib constants
20+
21+
# Test quality
22+
- testifylint # Testify usage
23+
24+
settings:
25+
goconst:
26+
min-occurrences: 3
27+
gosec:
28+
excludes:
29+
- G115 # Integer overflow - checked at runtime
30+
- G304 # File path from variable - paths are from config
31+
- G301 # Directory permissions 0755 - standard for non-sensitive
32+
- G306 # File permissions 0644 - standard for non-sensitive
33+
- G401 # MD5 - required by API protocol
34+
- G404 # math/rand - ok for sensor data simulation
35+
- G501 # MD5 import - required by API protocol
36+
37+
exclusions:
38+
rules:
39+
# Sensor data uses append to build new slices intentionally
40+
- path: internal/sensordata/
41+
linters:
42+
- gocritic
43+
text: appendAssign
44+
# Test files can have repeated strings
45+
- path: _test\.go
46+
linters:
47+
- goconst

internal/api/auth.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func (c *Client) GetEncryptionKeys(ctx context.Context) error {
200200
"Content-Type": "application/json",
201201
}
202202

203-
req, err := http.NewRequestWithContext(ctx, "POST", c.baseURL+EndpointCheckVersion, nil)
203+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+EndpointCheckVersion, nil)
204204
if err != nil {
205205
return fmt.Errorf("failed to create request: %w", err)
206206
}
@@ -256,7 +256,7 @@ func (c *Client) GetUsherEncryptionKey(ctx context.Context) (string, string, err
256256
"sdkVersion": []string{UsherSDKVersion},
257257
}
258258

259-
req, err := http.NewRequestWithContext(ctx, "GET", c.usherURL+EndpointEncryptionKey+"?"+params.Encode(), nil)
259+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.usherURL+EndpointEncryptionKey+"?"+params.Encode(), nil)
260260
if err != nil {
261261
return "", "", fmt.Errorf("failed to create request: %w", err)
262262
}
@@ -319,7 +319,7 @@ func (c *Client) Login(ctx context.Context) error {
319319
return fmt.Errorf("failed to marshal login data: %w", err)
320320
}
321321

322-
req, err := http.NewRequestWithContext(ctx, "POST", c.usherURL+EndpointLogin, bytes.NewBuffer(jsonData))
322+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.usherURL+EndpointLogin, bytes.NewBuffer(jsonData))
323323
if err != nil {
324324
return fmt.Errorf("failed to create request: %w", err)
325325
}

internal/api/client.go

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"io"
910
"net/http"
@@ -98,8 +99,9 @@ func genericRetry[T any](
9899
response, err := executeFunc(ctx, method, uri, queryParams, bodyParams, needsKeys, needsAuth)
99100
if err != nil {
100101
// Handle retryable errors
101-
switch err.(type) {
102-
case *EncryptionError:
102+
var encErr *EncryptionError
103+
var tokenErr *TokenExpiredError
104+
if errors.As(err, &encErr) {
103105
// Retrieve new encryption keys and retry
104106
if err := c.GetEncryptionKeys(ctx); err != nil {
105107
return zero, fmt.Errorf("failed to retrieve encryption keys: %w", err)
@@ -110,7 +112,7 @@ func genericRetry[T any](
110112
return zero, err
111113
}
112114
return genericRetry(ctx, c, method, uri, queryParams, bodyParams, needsKeys, needsAuth, retryCount+1, executeFunc)
113-
case *TokenExpiredError:
115+
} else if errors.As(err, &tokenErr) {
114116
// Login again and retry
115117
if err := c.Login(ctx); err != nil {
116118
return zero, fmt.Errorf("failed to login: %w", err)
@@ -121,9 +123,8 @@ func genericRetry[T any](
121123
return zero, err
122124
}
123125
return genericRetry(ctx, c, method, uri, queryParams, bodyParams, needsKeys, needsAuth, retryCount+1, executeFunc)
124-
default:
125-
return zero, err
126126
}
127+
return zero, err
127128
}
128129

129130
return response, nil
@@ -260,11 +261,12 @@ func (c *Client) executeAPIRequest(ctx context.Context, method, uri string, quer
260261
}
261262

262263
// Calculate signature
263-
if uri == EndpointCheckVersion {
264+
switch {
265+
case uri == EndpointCheckVersion:
264266
headers["sign"] = c.getSignFromTimestamp(timestamp)
265-
} else if method == "GET" {
267+
case method == http.MethodGet:
266268
headers["sign"] = c.getSignFromPayloadAndTimestamp(originalQueryStr, timestamp)
267-
} else if method == "POST" {
269+
case method == http.MethodPost:
268270
headers["sign"] = c.getSignFromPayloadAndTimestamp(originalBodyStr, timestamp)
269271
}
270272

internal/api/client_integration_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ func TestAPIRequest_RetryOnEncryptionError(t *testing.T) {
1919
requestCount++
2020

2121
var response map[string]interface{}
22-
if requestCount == 1 {
22+
switch {
23+
case requestCount == 1:
2324
// First request: return encryption error
2425
response = map[string]interface{}{
2526
"state": "E",
2627
"errorCode": 600001,
2728
"message": "Encryption error",
2829
}
29-
} else if requestCount == 2 && r.URL.Path == "/"+EndpointCheckVersion {
30+
case requestCount == 2 && r.URL.Path == "/"+EndpointCheckVersion:
3031
// Second request: return new keys
3132
testResponse := map[string]interface{}{
3233
"encKey": "newtestenckey123",
@@ -42,7 +43,7 @@ func TestAPIRequest_RetryOnEncryptionError(t *testing.T) {
4243
"state": "S",
4344
"payload": encrypted,
4445
}
45-
} else {
46+
default:
4647
// Subsequent request: return success
4748
testResponse := map[string]interface{}{
4849
"resultCode": "200S00",

internal/api/client_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package api
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"net/http"
78
"net/http/httptest"
89
"strings"
@@ -465,7 +466,7 @@ func TestAPIRequest_RetryWithContextCancellation(t *testing.T) {
465466
require.Error(t, err, "Expected error due to context cancellation, got nil")
466467

467468
// Check if error is or contains context.Canceled
468-
if err != context.Canceled {
469+
if !errors.Is(err, context.Canceled) {
469470
assert.Containsf(t, err.Error(), "context canceled", "Expected context.Canceled error, got: %v", err)
470471
}
471472

internal/api/errors_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package api
22

33
import (
4+
"errors"
45
"testing"
56

67
"github.com/stretchr/testify/assert"
@@ -67,7 +68,8 @@ func TestCheckResultCode_ReturnType(t *testing.T) {
6768
err := checkResultCode("500E00", "test operation")
6869
require.Error(t, err, "Expected error, got nil")
6970

70-
resultCodeErr, ok := err.(*ResultCodeError)
71+
resultCodeErr := &ResultCodeError{}
72+
ok := errors.As(err, &resultCodeErr)
7173
require.Truef(t, ok, "Expected *ResultCodeError, got %T", err)
7274

7375
assert.Equalf(t, "500E00", resultCodeErr.ResultCode, "Expected ResultCode '500E00', got '%s'", resultCodeErr.ResultCode)

internal/config/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"errors"
45
"fmt"
56
"os"
67
"path/filepath"
@@ -44,7 +45,8 @@ func Load(configPath string) (*Config, error) {
4445

4546
// Try to read config file (don't fail if it doesn't exist)
4647
if err := v.ReadInConfig(); err != nil {
47-
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
48+
var configFileNotFoundError viper.ConfigFileNotFoundError
49+
if !errors.As(err, &configFileNotFoundError) {
4850
return nil, fmt.Errorf("failed to read config file: %w", err)
4951
}
5052
}

internal/sensordata/touch_event.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,13 @@ func (t *TouchEventList) Randomize(sensorCollectionStartTimestamp time.Time) {
3939
nowTimestamp := time.Now().UTC()
4040
timeSinceSensorCollectionStart := int(nowTimestamp.Sub(sensorCollectionStartTimestamp).Milliseconds())
4141

42-
if timeSinceSensorCollectionStart < 3000 {
42+
switch {
43+
case timeSinceSensorCollectionStart < 3000:
4344
return
44-
} else if timeSinceSensorCollectionStart >= 3000 && timeSinceSensorCollectionStart < 5000 {
45+
case timeSinceSensorCollectionStart < 5000:
4546
downTime := timeSinceSensorCollectionStart - mathrand.Intn(1000) - 1000
4647
t.addTouchSequence(downTime)
47-
} else if timeSinceSensorCollectionStart >= 5000 && timeSinceSensorCollectionStart < 10000 {
48+
case timeSinceSensorCollectionStart < 10000:
4849
for i := 0; i < 2; i++ {
4950
timestampOffset := 0
5051
if i == 1 {
@@ -53,7 +54,7 @@ func (t *TouchEventList) Randomize(sensorCollectionStartTimestamp time.Time) {
5354
downTime := mathrand.Intn(900) + 100 + timestampOffset
5455
t.addTouchSequence(downTime)
5556
}
56-
} else {
57+
default:
5758
for i := 0; i < 3; i++ {
5859
timestampOffset := 0
5960
if i == 0 {

0 commit comments

Comments
 (0)