fix: clear golangci-lint backlog and make lint job blocking
Addresses errcheck, gosec, revive, staticcheck, and unused findings
across the codebase (unchecked error returns, unsafe file inclusion
warnings on operator/test-controlled paths, missing package comments,
unused parameters, deprecated API usage). Also fixes two suppression
comments that were silently no-ops due to wrong syntax (#nosec needs
a leading '#', nolint reasons need '//' not '--').
With the backlog clear, drop continue-on-error from the CI lint job
per the plan left in b4b4806.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+23
-18
@@ -1,8 +1,12 @@
|
||||
// Package grpc implements the gRPC transport for the notifier service,
|
||||
// translating between the generated protobuf types and internal domain
|
||||
// types.
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/google/uuid"
|
||||
pb "github.com/igodwin/notifier/api/grpc/pb"
|
||||
@@ -29,7 +33,7 @@ func NewNotifierHandler(svc domain.NotificationService, logger *logging.Logger)
|
||||
}
|
||||
|
||||
// HealthCheck verifies the service is operational
|
||||
func (h *NotifierHandler) HealthCheck(ctx context.Context, req *pb.HealthCheckRequest) (*pb.HealthCheckResponse, error) {
|
||||
func (h *NotifierHandler) HealthCheck(_ context.Context, _ *pb.HealthCheckRequest) (*pb.HealthCheckResponse, error) {
|
||||
// TODO: Implement proper health check logic
|
||||
return &pb.HealthCheckResponse{
|
||||
Healthy: true,
|
||||
@@ -56,7 +60,7 @@ func (h *NotifierHandler) SendNotification(ctx context.Context, req *pb.SendNoti
|
||||
}
|
||||
|
||||
// Convert content type, defaulting to text
|
||||
contentType := convertProtoContentTypeToDomain(req.ContentType)
|
||||
contentType := convertProtoContentTypeToDomain(req.ContentType) //nolint:staticcheck // deprecated content_type field still honored for backward compatibility
|
||||
|
||||
// Build notification
|
||||
notification := &domain.Notification{
|
||||
@@ -205,7 +209,7 @@ func (h *NotifierHandler) RetryNotification(ctx context.Context, req *pb.RetryNo
|
||||
}
|
||||
|
||||
// GetStats returns notification statistics
|
||||
func (h *NotifierHandler) GetStats(ctx context.Context, req *pb.GetStatsRequest) (*pb.GetStatsResponse, error) {
|
||||
func (h *NotifierHandler) GetStats(ctx context.Context, _ *pb.GetStatsRequest) (*pb.GetStatsResponse, error) {
|
||||
stats, err := h.service.GetStats(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -222,7 +226,7 @@ func (h *NotifierHandler) GetStats(ctx context.Context, req *pb.GetStatsRequest)
|
||||
}
|
||||
|
||||
// GetNotifiers returns information about available notifiers
|
||||
func (h *NotifierHandler) GetNotifiers(ctx context.Context, req *pb.GetNotifiersRequest) (*pb.GetNotifiersResponse, error) {
|
||||
func (h *NotifierHandler) GetNotifiers(ctx context.Context, _ *pb.GetNotifiersRequest) (*pb.GetNotifiersResponse, error) {
|
||||
h.logger.Infof("gRPC: Received request for available notifiers")
|
||||
|
||||
notifiers, err := h.service.GetNotifiers(ctx)
|
||||
@@ -248,6 +252,18 @@ func (h *NotifierHandler) GetNotifiers(ctx context.Context, req *pb.GetNotifiers
|
||||
|
||||
// Helper functions to convert between proto and domain types
|
||||
|
||||
// clampInt32 narrows an int to int32, saturating at the int32 bounds
|
||||
// instead of silently wrapping when the domain value is out of range.
|
||||
func clampInt32(v int) int32 {
|
||||
if v > math.MaxInt32 {
|
||||
return math.MaxInt32
|
||||
}
|
||||
if v < math.MinInt32 {
|
||||
return math.MinInt32
|
||||
}
|
||||
return int32(v)
|
||||
}
|
||||
|
||||
// convertStringMapToInterface converts proto's map[string]string to domain's map[string]interface{}
|
||||
func convertStringMapToInterface(m map[string]string) map[string]interface{} {
|
||||
if m == nil {
|
||||
@@ -315,17 +331,6 @@ func convertProtoContentTypeToDomain(protoType pb.ContentType) domain.ContentTyp
|
||||
}
|
||||
}
|
||||
|
||||
func convertDomainContentTypeToProto(domainType domain.ContentType) pb.ContentType {
|
||||
switch domainType {
|
||||
case domain.ContentTypeHTML:
|
||||
return pb.ContentType_CONTENT_TYPE_HTML
|
||||
case domain.ContentTypeText:
|
||||
return pb.ContentType_CONTENT_TYPE_TEXT
|
||||
default:
|
||||
return pb.ContentType_CONTENT_TYPE_TEXT
|
||||
}
|
||||
}
|
||||
|
||||
func convertDomainToProtoType(domainType domain.NotificationType) pb.NotificationType {
|
||||
switch domainType {
|
||||
case domain.TypeEmail:
|
||||
@@ -365,7 +370,7 @@ func convertDomainToProtoNotification(notif *domain.Notification) *pb.Notificati
|
||||
Id: notif.ID,
|
||||
Type: convertDomainToProtoType(notif.Type),
|
||||
Account: notif.Account,
|
||||
Priority: pb.Priority(notif.Priority),
|
||||
Priority: pb.Priority(clampInt32(int(notif.Priority))),
|
||||
Status: convertDomainToProtoStatus(notif.Status),
|
||||
Subject: notif.Subject,
|
||||
Body: notif.Body,
|
||||
@@ -373,8 +378,8 @@ func convertDomainToProtoNotification(notif *domain.Notification) *pb.Notificati
|
||||
Recipients: notif.Recipients,
|
||||
Metadata: convertInterfaceMapToString(notif.Metadata),
|
||||
CreatedAt: timestamppb.New(notif.CreatedAt),
|
||||
RetryCount: int32(notif.RetryCount),
|
||||
MaxRetries: int32(notif.MaxRetries),
|
||||
RetryCount: clampInt32(notif.RetryCount),
|
||||
MaxRetries: clampInt32(notif.MaxRetries),
|
||||
LastError: notif.LastError,
|
||||
}
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ func TestCORSMiddleware_AllowedOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
middleware := newCORSMiddleware(config)
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
}))
|
||||
|
||||
tests := []struct {
|
||||
@@ -94,9 +94,9 @@ func TestCORSMiddleware_BlockedOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
middleware := newCORSMiddleware(config)
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("OK"))
|
||||
_, _ = w.Write([]byte("OK"))
|
||||
}))
|
||||
|
||||
tests := []struct {
|
||||
@@ -158,7 +158,7 @@ func TestCORSMiddleware_PreflightRequest(t *testing.T) {
|
||||
}
|
||||
|
||||
middleware := newCORSMiddleware(config)
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := middleware(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {
|
||||
t.Error("Handler should not be called for OPTIONS request")
|
||||
}))
|
||||
|
||||
@@ -217,7 +217,7 @@ func TestCORSMiddleware_Credentials(t *testing.T) {
|
||||
}
|
||||
|
||||
middleware := newCORSMiddleware(config)
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
@@ -243,7 +243,7 @@ func TestCORSMiddleware_NoWildcard(t *testing.T) {
|
||||
}
|
||||
|
||||
middleware := newCORSMiddleware(config)
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
@@ -273,7 +273,7 @@ func TestCORSMiddleware_EmptyConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
middleware := newCORSMiddleware(config)
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
@@ -355,7 +355,7 @@ func TestCORSMiddleware_MaxAge(t *testing.T) {
|
||||
}
|
||||
|
||||
middleware := newCORSMiddleware(config)
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := middleware(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ func (h *Handler) GetNotifiers(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// HealthCheck handles GET /health
|
||||
func (h *Handler) HealthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *Handler) HealthCheck(w http.ResponseWriter, _ *http.Request) {
|
||||
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "healthy",
|
||||
"service": "notifier",
|
||||
|
||||
+13
-5
@@ -306,7 +306,7 @@ func (h *KeyManagementHandler) GetAuditLog(w http.ResponseWriter, r *http.Reques
|
||||
// Helper methods
|
||||
|
||||
// hasRole checks if the auth context has a specific role
|
||||
func (h *KeyManagementHandler) hasRole(authCtx *auth.AuthContext, role string) bool {
|
||||
func (h *KeyManagementHandler) hasRole(authCtx *auth.Context, role string) bool {
|
||||
for _, r := range authCtx.Roles {
|
||||
if r == role {
|
||||
return true
|
||||
@@ -319,16 +319,24 @@ func (h *KeyManagementHandler) hasRole(authCtx *auth.AuthContext, role string) b
|
||||
func (h *KeyManagementHandler) respondJSON(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(data)
|
||||
// Headers are already written at this point, so there's nothing left to
|
||||
// do but log an encode failure.
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
h.logger.Errorf("Failed to encode JSON response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// respondError writes an error JSON response
|
||||
func (h *KeyManagementHandler) respondError(w http.ResponseWriter, statusCode int, error string, message string) {
|
||||
func (h *KeyManagementHandler) respondError(w http.ResponseWriter, statusCode int, errMsg string, message string) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
resp := ErrorResponse{
|
||||
Error: error,
|
||||
Error: errMsg,
|
||||
Message: message,
|
||||
}
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
// Headers are already written at this point, so there's nothing left to
|
||||
// do but log an encode failure.
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Errorf("Failed to encode JSON error response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -1,3 +1,6 @@
|
||||
// Package rest implements the HTTP/JSON transport for the notifier
|
||||
// service: request routing, handlers, authentication and CORS middleware,
|
||||
// and API key management endpoints.
|
||||
package rest
|
||||
|
||||
import (
|
||||
@@ -138,9 +141,11 @@ func NewRouterWithOptions(opts RouterOptions) *mux.Router {
|
||||
// LivenessHandler returns a minimal liveness handler for dedicated health
|
||||
// listeners (the REST router serves the same signal at /health).
|
||||
func LivenessHandler() http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
// Headers are already written, so an encode error can only be
|
||||
// dropped on the floor here.
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "healthy",
|
||||
"service": "notifier",
|
||||
"time": time.Now().UTC(),
|
||||
@@ -173,7 +178,9 @@ func readinessHandler(checks map[string]ReadinessCheck) http.HandlerFunc {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
ready := status == http.StatusOK
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
// Headers are already written, so an encode error can only be
|
||||
// dropped on the floor here.
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"ready": ready,
|
||||
"components": components,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user