NewUnaryHandler constructs a [Handler] for a request-response procedure.
( procedure string, unary func(context.Context, *Request[Req]) (*Response[Res], error), options ...HandlerOption, )
| 35 | |
| 36 | // NewUnaryHandler constructs a [Handler] for a request-response procedure. |
| 37 | func NewUnaryHandler[Req, Res any]( |
| 38 | procedure string, |
| 39 | unary func(context.Context, *Request[Req]) (*Response[Res], error), |
| 40 | options ...HandlerOption, |
| 41 | ) *Handler { |
| 42 | // Wrap the strongly-typed implementation so we can apply interceptors. |
| 43 | untyped := UnaryFunc(func(ctx context.Context, request AnyRequest) (AnyResponse, error) { |
| 44 | if err := ctx.Err(); err != nil { |
| 45 | return nil, err |
| 46 | } |
| 47 | typed, ok := request.(*Request[Req]) |
| 48 | if !ok { |
| 49 | return nil, errorf(CodeInternal, "unexpected handler request type %T", request) |
| 50 | } |
| 51 | res, err := unary(ctx, typed) |
| 52 | if res == nil && err == nil { |
| 53 | // This is going to panic during serialization. Debugging is much easier |
| 54 | // if we panic here instead, so we can include the procedure name. |
| 55 | panic(procedure + " returned nil *connect.Response and nil error") //nolint: forbidigo |
| 56 | } |
| 57 | if res == nil { |
| 58 | // Avoid returning a typed nil (*Response[Res]) as an AnyResponse interface value. |
| 59 | return nil, err |
| 60 | } |
| 61 | return res, err |
| 62 | }) |
| 63 | config := newHandlerConfig(procedure, StreamTypeUnary, options) |
| 64 | if interceptor := config.Interceptor; interceptor != nil { |
| 65 | untyped = interceptor.WrapUnary(untyped) |
| 66 | } |
| 67 | // Given a stream, how should we call the unary function? |
| 68 | implementation := func(ctx context.Context, conn StreamingHandlerConn) error { |
| 69 | request, err := receiveUnaryRequest[Req](conn, config.Initializer) |
| 70 | if err != nil { |
| 71 | return err |
| 72 | } |
| 73 | // Add the request header to the context, and store the response header |
| 74 | // and trailer to propagate back to the caller. |
| 75 | info := &handlerCallInfo{ |
| 76 | peer: request.Peer(), |
| 77 | spec: request.Spec(), |
| 78 | method: request.HTTPMethod(), |
| 79 | requestHeader: request.Header(), |
| 80 | } |
| 81 | ctx = newHandlerContext(ctx, info) |
| 82 | response, err := untyped(ctx, request) |
| 83 | // Add response headers/trailers from the context callinfo into the conn if they exist |
| 84 | if info.responseHeader != nil { |
| 85 | mergeNonProtocolHeaders(conn.ResponseHeader(), info.responseHeader) |
| 86 | } |
| 87 | if info.responseTrailer != nil { |
| 88 | mergeNonProtocolHeaders(conn.ResponseTrailer(), info.responseTrailer) |
| 89 | } |
| 90 | if err != nil { |
| 91 | return err |
| 92 | } |
| 93 | |
| 94 | // Add response headers/trailers from the response into the conn if they exist |