fix(backend): correct generic type parameter in withHeaderCarrier helper

Problem:
The withHeaderCarrier generic function had a type mismatch that caused compilation
errors in CI. The function used `T proto.Message` constraint, but Connect's Response
type expects the non-pointer message type while protobuf methods return pointers.

Error from CI:
  type T of resp does not match *T (cannot infer T)

This occurred because:
- Connect methods expect: *connect.Response[v1pb.CreateSessionResponse]
- Service methods return: (*v1pb.CreateSessionResponse, error)
- Old signature: fn func(context.Context) (T, error) with T proto.Message
- This caused T to be inferred as *v1pb.CreateSessionResponse
- Leading to return type: *connect.Response[*v1pb.CreateSessionResponse] (wrong!)

Solution:
Changed generic signature to explicitly handle the pointer/non-pointer distinction:
- New signature: fn func(context.Context) (*T, error) with T any
- T is now the non-pointer type (e.g., v1pb.CreateSessionResponse)
- fn returns *T (e.g., *v1pb.CreateSessionResponse)
- Return type is correctly: *connect.Response[T] (e.g., *connect.Response[v1pb.CreateSessionResponse])

Also removed unused "google.golang.org/protobuf/proto" import and improved documentation
to clarify the T vs *T distinction.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Steven 2025-12-12 08:03:39 +08:00
parent 06a0f8d618
commit 65a19df4be
2 changed files with 11 additions and 9 deletions

View File

@ -42,23 +42,23 @@ func (s *ConnectServiceHandler) UpdateInstanceSetting(ctx context.Context, req *
// AuthService // AuthService
// //
// Auth service methods need special handling for response headers (cookies). // Auth service methods need special handling for response headers (cookies).
// We use withHeaderCarrier helper to inject a header carrier into the context, // We use connectWithHeaderCarrier helper to inject a header carrier into the context,
// which allows the service to set headers in a protocol-agnostic way. // which allows the service to set headers in a protocol-agnostic way.
func (s *ConnectServiceHandler) GetCurrentSession(ctx context.Context, req *connect.Request[v1pb.GetCurrentSessionRequest]) (*connect.Response[v1pb.GetCurrentSessionResponse], error) { func (s *ConnectServiceHandler) GetCurrentSession(ctx context.Context, req *connect.Request[v1pb.GetCurrentSessionRequest]) (*connect.Response[v1pb.GetCurrentSessionResponse], error) {
return withHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.GetCurrentSessionResponse, error) { return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.GetCurrentSessionResponse, error) {
return s.APIV1Service.GetCurrentSession(ctx, req.Msg) return s.APIV1Service.GetCurrentSession(ctx, req.Msg)
}) })
} }
func (s *ConnectServiceHandler) CreateSession(ctx context.Context, req *connect.Request[v1pb.CreateSessionRequest]) (*connect.Response[v1pb.CreateSessionResponse], error) { func (s *ConnectServiceHandler) CreateSession(ctx context.Context, req *connect.Request[v1pb.CreateSessionRequest]) (*connect.Response[v1pb.CreateSessionResponse], error) {
return withHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.CreateSessionResponse, error) { return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.CreateSessionResponse, error) {
return s.APIV1Service.CreateSession(ctx, req.Msg) return s.APIV1Service.CreateSession(ctx, req.Msg)
}) })
} }
func (s *ConnectServiceHandler) DeleteSession(ctx context.Context, req *connect.Request[v1pb.DeleteSessionRequest]) (*connect.Response[emptypb.Empty], error) { func (s *ConnectServiceHandler) DeleteSession(ctx context.Context, req *connect.Request[v1pb.DeleteSessionRequest]) (*connect.Response[emptypb.Empty], error) {
return withHeaderCarrier(ctx, func(ctx context.Context) (*emptypb.Empty, error) { return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*emptypb.Empty, error) {
return s.APIV1Service.DeleteSession(ctx, req.Msg) return s.APIV1Service.DeleteSession(ctx, req.Msg)
}) })
} }

View File

@ -6,7 +6,6 @@ import (
"connectrpc.com/connect" "connectrpc.com/connect"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
) )
// headerCarrierKey is the context key for storing headers to be set in the response. // headerCarrierKey is the context key for storing headers to be set in the response.
@ -86,19 +85,22 @@ func SetResponseHeader(ctx context.Context, key, value string) error {
})) }))
} }
// withHeaderCarrier is a helper for Connect service wrappers that need to set response headers. // connectWithHeaderCarrier is a helper for Connect service wrappers that need to set response headers.
// //
// It injects a HeaderCarrier into the context, calls the service method, // It injects a HeaderCarrier into the context, calls the service method,
// and applies any headers from the carrier to the Connect response. // and applies any headers from the carrier to the Connect response.
// //
// The generic parameter T is the non-pointer protobuf message type (e.g., v1pb.CreateSessionResponse),
// while fn returns *T (the pointer type) as is standard for protobuf messages.
//
// Usage in Connect wrappers: // Usage in Connect wrappers:
// //
// func (s *ConnectServiceHandler) CreateSession(ctx context.Context, req *connect.Request[...]) (*connect.Response[...], error) { // func (s *ConnectServiceHandler) CreateSession(ctx context.Context, req *connect.Request[v1pb.CreateSessionRequest]) (*connect.Response[v1pb.CreateSessionResponse], error) {
// return withHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.CreateSessionResponse, error) { // return connectWithHeaderCarrier(ctx, func(ctx context.Context) (*v1pb.CreateSessionResponse, error) {
// return s.APIV1Service.CreateSession(ctx, req.Msg) // return s.APIV1Service.CreateSession(ctx, req.Msg)
// }) // })
// } // }
func withHeaderCarrier[T proto.Message](ctx context.Context, fn func(context.Context) (T, error)) (*connect.Response[T], error) { func connectWithHeaderCarrier[T any](ctx context.Context, fn func(context.Context) (*T, error)) (*connect.Response[T], error) {
// Inject header carrier for Connect protocol // Inject header carrier for Connect protocol
ctx = WithHeaderCarrier(ctx) ctx = WithHeaderCarrier(ctx)