diff --git a/.github/workflows/transport-http.yml b/.github/workflows/transport-http.yml new file mode 100644 index 0000000..6accaa9 --- /dev/null +++ b/.github/workflows/transport-http.yml @@ -0,0 +1,30 @@ +name: Checks +on: + pull_request: + # branches: + # - main + paths: + - 'transport/http/**' + +jobs: + quality-check: + name: Quality Check + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + # with: + # fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.21.6' + - name: Lint + uses: golangci/golangci-lint-action@v4 + with: + version: v1.56.2 + args: --out-format checkstyle:lint-report.xml,github-actions --timeout 2m --tests=false + working-directory: './transport/http' + - name: Test + run: go test -v -timeout 30s -count=1 ./... -coverprofile=test-report.out + working-directory: './transport/http' diff --git a/transport/http/client.go b/transport/http/client.go index 7d868ee..670770e 100644 --- a/transport/http/client.go +++ b/transport/http/client.go @@ -4,13 +4,11 @@ import ( "bytes" "context" "encoding/json" - "encoding/xml" "io" - "io/ioutil" "net/http" "net/url" - "github.com/go-kit/kit/endpoint" + gkit "github.com/kikihakiem/gkit/core" ) // HTTPClient is an interface that models *http.Client. @@ -19,10 +17,10 @@ type HTTPClient interface { } // Client wraps a URL and provides a method that implements endpoint.Endpoint. -type Client struct { +type Client[Req, Res any] struct { client HTTPClient - req CreateRequestFunc - dec DecodeResponseFunc + req gkit.EncodeDecodeFunc[Req, *http.Request] + dec gkit.EncodeDecodeFunc[*http.Response, Res] before []RequestFunc after []ClientResponseFunc finalizer []ClientFinalizerFunc @@ -30,15 +28,15 @@ type Client struct { } // NewClient constructs a usable Client for a single remote method. -func NewClient(method string, tgt *url.URL, enc EncodeRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client { - return NewExplicitClient(makeCreateRequestFunc(method, tgt, enc), dec, options...) +func NewClient[Req, Res any](method string, tgt *url.URL, enc EncodeRequestFunc[Req], dec gkit.EncodeDecodeFunc[*http.Response, Res], options ...ClientOption[Req, Res]) *Client[Req, Res] { + return NewExplicitClient[Req, Res](makeCreateRequestFunc(method, tgt, enc), dec, options...) } // NewExplicitClient is like NewClient but uses a CreateRequestFunc instead of a // method, target URL, and EncodeRequestFunc, which allows for more control over // the outgoing HTTP request. -func NewExplicitClient(req CreateRequestFunc, dec DecodeResponseFunc, options ...ClientOption) *Client { - c := &Client{ +func NewExplicitClient[Req, Res any](req gkit.EncodeDecodeFunc[Req, *http.Request], dec gkit.EncodeDecodeFunc[*http.Response, Res], options ...ClientOption[Req, Res]) *Client[Req, Res] { + c := &Client[Req, Res]{ client: http.DefaultClient, req: req, dec: dec, @@ -50,50 +48,51 @@ func NewExplicitClient(req CreateRequestFunc, dec DecodeResponseFunc, options .. } // ClientOption sets an optional parameter for clients. -type ClientOption func(*Client) +type ClientOption[Req, Res any] gkit.Option[*Client[Req, Res]] // SetClient sets the underlying HTTP client used for requests. // By default, http.DefaultClient is used. -func SetClient(client HTTPClient) ClientOption { - return func(c *Client) { c.client = client } +func SetClient[Req, Res any](client HTTPClient) ClientOption[Req, Res] { + return func(c *Client[Req, Res]) { c.client = client } } // ClientBefore adds one or more RequestFuncs to be applied to the outgoing HTTP // request before it's invoked. -func ClientBefore(before ...RequestFunc) ClientOption { - return func(c *Client) { c.before = append(c.before, before...) } +func ClientBefore[Req, Res any](before ...RequestFunc) ClientOption[Req, Res] { + return func(c *Client[Req, Res]) { c.before = append(c.before, before...) } } // ClientAfter adds one or more ClientResponseFuncs, which are applied to the // incoming HTTP response prior to it being decoded. This is useful for // obtaining anything off of the response and adding it into the context prior // to decoding. -func ClientAfter(after ...ClientResponseFunc) ClientOption { - return func(c *Client) { c.after = append(c.after, after...) } +func ClientAfter[Req, Res any](after ...ClientResponseFunc) ClientOption[Req, Res] { + return func(c *Client[Req, Res]) { c.after = append(c.after, after...) } } // ClientFinalizer adds one or more ClientFinalizerFuncs to be executed at the // end of every HTTP request. Finalizers are executed in the order in which they // were added. By default, no finalizer is registered. -func ClientFinalizer(f ...ClientFinalizerFunc) ClientOption { - return func(s *Client) { s.finalizer = append(s.finalizer, f...) } +func ClientFinalizer[Req, Res any](f ...ClientFinalizerFunc) ClientOption[Req, Res] { + return func(s *Client[Req, Res]) { s.finalizer = append(s.finalizer, f...) } } // BufferedStream sets whether the HTTP response body is left open, allowing it // to be read from later. Useful for transporting a file as a buffered stream. // That body has to be drained and closed to properly end the request. -func BufferedStream(buffered bool) ClientOption { - return func(c *Client) { c.bufferedStream = buffered } +func BufferedStream[Req, Res any](buffered bool) ClientOption[Req, Res] { + return func(c *Client[Req, Res]) { c.bufferedStream = buffered } } // Endpoint returns a usable Go kit endpoint that calls the remote HTTP endpoint. -func (c Client) Endpoint() endpoint.Endpoint { - return func(ctx context.Context, request interface{}) (interface{}, error) { +func (c Client[Req, Res]) Endpoint() gkit.Endpoint[Req, Res] { + return func(ctx context.Context, request Req) (Res, error) { ctx, cancel := context.WithCancel(ctx) var ( - resp *http.Response - err error + resp *http.Response + response Res + err error ) if c.finalizer != nil { defer func() { @@ -110,7 +109,7 @@ func (c Client) Endpoint() endpoint.Endpoint { req, err := c.req(ctx, request) if err != nil { cancel() - return nil, err + return response, err } for _, f := range c.before { @@ -120,7 +119,7 @@ func (c Client) Endpoint() endpoint.Endpoint { resp, err = c.client.Do(req.WithContext(ctx)) if err != nil { cancel() - return nil, err + return response, err } // If the caller asked for a buffered stream, we don't cancel the @@ -137,9 +136,9 @@ func (c Client) Endpoint() endpoint.Endpoint { ctx = f(ctx, resp) } - response, err := c.dec(ctx, resp) + response, err = c.dec(ctx, resp) if err != nil { - return nil, err + return response, err } return response, nil @@ -170,41 +169,18 @@ type ClientFinalizerFunc func(ctx context.Context, err error) // EncodeJSONRequest is an EncodeRequestFunc that serializes the request as a // JSON object to the Request body. Many JSON-over-HTTP services can use it as -// a sensible default. If the request implements Headerer, the provided headers +// a sensible default. TODO: If the request implements Headerer, the provided headers // will be applied to the request. -func EncodeJSONRequest(c context.Context, r *http.Request, request interface{}) error { +func EncodeJSONRequest[Req any](c context.Context, r *http.Request, request Req) error { r.Header.Set("Content-Type", "application/json; charset=utf-8") - if headerer, ok := request.(Headerer); ok { - for k := range headerer.Headers() { - r.Header.Set(k, headerer.Headers().Get(k)) - } - } - var b bytes.Buffer - r.Body = ioutil.NopCloser(&b) - return json.NewEncoder(&b).Encode(request) -} -// EncodeXMLRequest is an EncodeRequestFunc that serializes the request as a -// XML object to the Request body. If the request implements Headerer, -// the provided headers will be applied to the request. -func EncodeXMLRequest(c context.Context, r *http.Request, request interface{}) error { - r.Header.Set("Content-Type", "text/xml; charset=utf-8") - if headerer, ok := request.(Headerer); ok { - for k := range headerer.Headers() { - r.Header.Set(k, headerer.Headers().Get(k)) - } - } var b bytes.Buffer - r.Body = ioutil.NopCloser(&b) - return xml.NewEncoder(&b).Encode(request) + r.Body = io.NopCloser(&b) + return json.NewEncoder(&b).Encode(request) } -// -// -// - -func makeCreateRequestFunc(method string, target *url.URL, enc EncodeRequestFunc) CreateRequestFunc { - return func(ctx context.Context, request interface{}) (*http.Request, error) { +func makeCreateRequestFunc[Req any](method string, target *url.URL, enc EncodeRequestFunc[Req]) gkit.EncodeDecodeFunc[Req, *http.Request] { + return func(ctx context.Context, request Req) (*http.Request, error) { req, err := http.NewRequest(method, target.String(), nil) if err != nil { return nil, err diff --git a/transport/http/client_test.go b/transport/http/client_test.go index 3748546..6385669 100644 --- a/transport/http/client_test.go +++ b/transport/http/client_test.go @@ -1,3 +1,5 @@ +//go:build unit + package http_test import ( @@ -5,7 +7,6 @@ import ( "context" "fmt" "io" - "io/ioutil" "net/http" "net/http/httptest" "net/url" @@ -13,7 +14,7 @@ import ( "testing" "time" - httptransport "github.com/go-kit/kit/transport/http" + httptransport "github.com/kikihakiem/gkit/transport/http" ) type TestResponse struct { @@ -24,8 +25,8 @@ type TestResponse struct { func TestHTTPClient(t *testing.T) { var ( testbody = "testbody" - encode = func(context.Context, *http.Request, interface{}) error { return nil } - decode = func(_ context.Context, r *http.Response) (interface{}, error) { + encode = func(context.Context, *http.Request, struct{}) error { return nil } + decode = func(_ context.Context, r *http.Response) (TestResponse, error) { buffer := make([]byte, len(testbody)) r.Body.Read(buffer) return TestResponse{r.Body, string(buffer)}, nil @@ -49,13 +50,13 @@ func TestHTTPClient(t *testing.T) { w.Write([]byte(testbody)) })) - client := httptransport.NewClient( + client := httptransport.NewClient[struct{}, TestResponse]( "GET", mustParse(server.URL), encode, decode, - httptransport.ClientBefore(httptransport.SetRequestHeader(headerKey, headerVal)), - httptransport.ClientAfter(afterFunc), + httptransport.ClientBefore[struct{}, TestResponse](httptransport.SetRequestHeader(headerKey, headerVal)), + httptransport.ClientAfter[struct{}, TestResponse](afterFunc), ) res, err := client.Endpoint()(context.Background(), struct{}{}) @@ -79,18 +80,13 @@ func TestHTTPClient(t *testing.T) { t.Errorf("want %q, have %q", want, have) } - // Check that the response was successfully decoded - response, ok := res.(TestResponse) - if !ok { - t.Fatal("response should be TestResponse") - } - if want, have := testbody, response.String; want != have { + if want, have := testbody, res.String; want != have { t.Errorf("want %q, have %q", want, have) } // Check that response body was closed b := make([]byte, 1) - _, err = response.Body.Read(b) + _, err = res.Body.Read(b) if err == nil { t.Fatal("wanted error, got none") } @@ -106,8 +102,8 @@ func TestHTTPClientBufferedStream(t *testing.T) { const bodysize = 6000 var ( testbody = string(make([]byte, bodysize)) - encode = func(context.Context, *http.Request, interface{}) error { return nil } - decode = func(_ context.Context, r *http.Response) (interface{}, error) { + encode = func(context.Context, *http.Request, struct{}) error { return nil } + decode = func(_ context.Context, r *http.Response) (TestResponse, error) { return TestResponse{r.Body, ""}, nil } ) @@ -117,12 +113,12 @@ func TestHTTPClientBufferedStream(t *testing.T) { w.Write([]byte(testbody)) })) - client := httptransport.NewClient( + client := httptransport.NewClient[struct{}, TestResponse]( "GET", mustParse(server.URL), encode, decode, - httptransport.BufferedStream(true), + httptransport.BufferedStream[struct{}, TestResponse](true), ) res, err := client.Endpoint()(context.Background(), struct{}{}) @@ -130,18 +126,13 @@ func TestHTTPClientBufferedStream(t *testing.T) { t.Fatal(err) } - // Check that the response was successfully decoded - response, ok := res.(TestResponse) - if !ok { - t.Fatal("response should be TestResponse") - } - defer response.Body.Close() + defer res.Body.Close() // Faking work time.Sleep(time.Second * 1) // Check that response body was NOT closed b := make([]byte, len(testbody)) - _, err = response.Body.Read(b) + _, err = res.Body.Read(b) if want, have := io.EOF, err; have != want { t.Fatalf("want %q, have %q", want, have) } @@ -156,8 +147,8 @@ func TestClientFinalizer(t *testing.T) { headerVal = "Helllo you stinky lizard" responseBody = "go eat a fly ugly\n" done = make(chan struct{}) - encode = func(context.Context, *http.Request, interface{}) error { return nil } - decode = func(_ context.Context, r *http.Response) (interface{}, error) { + encode = func(context.Context, *http.Request, struct{}) error { return nil } + decode = func(_ context.Context, r *http.Response) (TestResponse, error) { return TestResponse{r.Body, ""}, nil } ) @@ -168,12 +159,12 @@ func TestClientFinalizer(t *testing.T) { })) defer server.Close() - client := httptransport.NewClient( + client := httptransport.NewClient[struct{}, TestResponse]( "GET", mustParse(server.URL), encode, decode, - httptransport.ClientFinalizer(func(ctx context.Context, err error) { + httptransport.ClientFinalizer[struct{}, TestResponse](func(ctx context.Context, err error) { responseHeader := ctx.Value(httptransport.ContextKeyResponseHeaders).(http.Header) if want, have := headerVal, responseHeader.Get(headerKey); want != have { t.Errorf("%s: want %q, have %q", headerKey, want, have) @@ -201,27 +192,25 @@ func TestClientFinalizer(t *testing.T) { } func TestEncodeJSONRequest(t *testing.T) { - var header http.Header var body string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - b, err := ioutil.ReadAll(r.Body) + b, err := io.ReadAll(r.Body) if err != nil && err != io.EOF { t.Fatal(err) } - header = r.Header + body = string(b) })) defer server.Close() serverURL, err := url.Parse(server.URL) - if err != nil { t.Fatal(err) } - client := httptransport.NewClient( + client := httptransport.NewClient[any, any]( "POST", serverURL, httptransport.EncodeJSONRequest, @@ -248,27 +237,15 @@ func TestEncodeJSONRequest(t *testing.T) { t.Errorf("%v: actual %#v, expected %#v", test.value, body, test.body) } } - - if _, err := client(context.Background(), enhancedRequest{Foo: "foo"}); err != nil { - t.Fatal(err) - } - - if _, ok := header["X-Edward"]; !ok { - t.Fatalf("X-Edward value: actual %v, expected %v", nil, []string{"Snowden"}) - } - - if v := header.Get("X-Edward"); v != "Snowden" { - t.Errorf("X-Edward string: actual %v, expected %v", v, "Snowden") - } } func TestSetClient(t *testing.T) { var ( encode = func(context.Context, *http.Request, interface{}) error { return nil } - decode = func(_ context.Context, r *http.Response) (interface{}, error) { - t, err := ioutil.ReadAll(r.Body) + decode = func(_ context.Context, r *http.Response) (string, error) { + t, err := io.ReadAll(r.Body) if err != nil { - return nil, err + return "", err } return string(t), nil } @@ -278,23 +255,23 @@ func TestSetClient(t *testing.T) { return &http.Response{ StatusCode: http.StatusOK, Request: req, - Body: ioutil.NopCloser(bytes.NewBufferString("hello, world!")), + Body: io.NopCloser(bytes.NewBufferString("hello, world!")), }, nil }) - client := httptransport.NewClient( + client := httptransport.NewClient[any, string]( "GET", &url.URL{}, encode, decode, - httptransport.SetClient(testHttpClient), + httptransport.SetClient[any, string](testHttpClient), ).Endpoint() resp, err := client(context.Background(), nil) if err != nil { t.Fatal(err) } - if r, ok := resp.(string); !ok || r != "hello, world!" { + if resp != "hello, world!" { t.Fatal("Expected response to be 'hello, world!' string") } } @@ -311,7 +288,7 @@ func TestNewExplicitClient(t *testing.T) { } dec := func(_ context.Context, resp *http.Response) (response interface{}, err error) { - buf, err := ioutil.ReadAll(resp.Body) + buf, err := io.ReadAll(resp.Body) resp.Body.Close() return string(buf), err } diff --git a/transport/http/doc.go b/transport/http/doc.go index e640103..1716da6 100644 --- a/transport/http/doc.go +++ b/transport/http/doc.go @@ -1,2 +1,2 @@ -// Package http provides a general purpose HTTP binding for endpoints. +// Package http provides a general purpose HTTP binding for gkit.Endpoint. package http diff --git a/transport/http/encode_decode.go b/transport/http/encode_decode.go index b3de462..0d6ffda 100644 --- a/transport/http/encode_decode.go +++ b/transport/http/encode_decode.go @@ -5,32 +5,14 @@ import ( "net/http" ) -// DecodeRequestFunc extracts a user-domain request object from an HTTP -// request object. It's designed to be used in HTTP servers, for server-side -// endpoints. One straightforward DecodeRequestFunc could be something that -// JSON decodes from the request body to the concrete request type. -type DecodeRequestFunc func(context.Context, *http.Request) (request interface{}, err error) - // EncodeRequestFunc encodes the passed request object into the HTTP request // object. It's designed to be used in HTTP clients, for client-side // endpoints. One straightforward EncodeRequestFunc could be something that JSON // encodes the object directly to the request body. -type EncodeRequestFunc func(context.Context, *http.Request, interface{}) error - -// CreateRequestFunc creates an outgoing HTTP request based on the passed -// request object. It's designed to be used in HTTP clients, for client-side -// endpoints. It's a more powerful version of EncodeRequestFunc, and can be used -// if more fine-grained control of the HTTP request is required. -type CreateRequestFunc func(context.Context, interface{}) (*http.Request, error) +type EncodeRequestFunc[Req any] func(context.Context, *http.Request, Req) error // EncodeResponseFunc encodes the passed response object to the HTTP response // writer. It's designed to be used in HTTP servers, for server-side // endpoints. One straightforward EncodeResponseFunc could be something that // JSON encodes the object directly to the response body. -type EncodeResponseFunc func(context.Context, http.ResponseWriter, interface{}) error - -// DecodeResponseFunc extracts a user-domain response object from an HTTP -// response object. It's designed to be used in HTTP clients, for client-side -// endpoints. One straightforward DecodeResponseFunc could be something that -// JSON decodes from the response body to the concrete response type. -type DecodeResponseFunc func(context.Context, *http.Response) (response interface{}, err error) +type EncodeResponseFunc[Res any] func(context.Context, http.ResponseWriter, Res) error diff --git a/transport/http/example_test.go b/transport/http/example_test.go index 3889354..440fd3b 100644 --- a/transport/http/example_test.go +++ b/transport/http/example_test.go @@ -1,24 +1,28 @@ -package http +//go:build unit + +package http_test import ( "context" "fmt" "net/http" "net/http/httptest" + + httptransport "github.com/kikihakiem/gkit/transport/http" ) func ExamplePopulateRequestContext() { - handler := NewServer( - func(ctx context.Context, request interface{}) (response interface{}, err error) { - fmt.Println("Method", ctx.Value(ContextKeyRequestMethod).(string)) - fmt.Println("RequestPath", ctx.Value(ContextKeyRequestPath).(string)) - fmt.Println("RequestURI", ctx.Value(ContextKeyRequestURI).(string)) - fmt.Println("X-Request-ID", ctx.Value(ContextKeyRequestXRequestID).(string)) + handler := httptransport.NewServer[struct{}, struct{}]( + func(ctx context.Context, request struct{}) (response struct{}, err error) { + fmt.Println("Method", ctx.Value(httptransport.ContextKeyRequestMethod).(string)) + fmt.Println("RequestPath", ctx.Value(httptransport.ContextKeyRequestPath).(string)) + fmt.Println("RequestURI", ctx.Value(httptransport.ContextKeyRequestURI).(string)) + fmt.Println("X-Request-ID", ctx.Value(httptransport.ContextKeyRequestXRequestID).(string)) return struct{}{}, nil }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, - func(context.Context, http.ResponseWriter, interface{}) error { return nil }, - ServerBefore(PopulateRequestContext), + func(context.Context, *http.Request) (struct{}, error) { return struct{}{}, nil }, + func(context.Context, http.ResponseWriter, struct{}) error { return nil }, + httptransport.ServerBefore[struct{}, struct{}](httptransport.PopulateRequestContext), ) server := httptest.NewServer(handler) diff --git a/transport/http/go.mod b/transport/http/go.mod index 23035b0..d8cffd2 100644 --- a/transport/http/go.mod +++ b/transport/http/go.mod @@ -2,9 +2,4 @@ module github.com/kikihakiem/gkit/transport/http go 1.21.6 -require ( - github.com/go-kit/kit v0.13.0 - github.com/go-kit/log v0.2.1 -) - -require github.com/go-logfmt/logfmt v0.5.1 // indirect +require github.com/kikihakiem/gkit/core v0.2.0 diff --git a/transport/http/go.sum b/transport/http/go.sum index ffae8fc..0c656c5 100644 --- a/transport/http/go.sum +++ b/transport/http/go.sum @@ -1,6 +1,2 @@ -github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= -github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/kikihakiem/gkit/core v0.2.0 h1:ZXgsJlc/cdIg++nuIlJZjnnO3cyuGYitcbp2c8Sa6B4= +github.com/kikihakiem/gkit/core v0.2.0/go.mod h1:PjK77BVx0+eVzPqx4U9I0FT0ljRSenyKBDiM5KO4/oY= diff --git a/transport/http/intercepting_writer.go b/transport/http/intercepting_writer.go index 3737b6e..0b0cebc 100644 --- a/transport/http/intercepting_writer.go +++ b/transport/http/intercepting_writer.go @@ -27,161 +27,108 @@ func (w *interceptingWriter) Write(p []byte) (int, error) { // reimplementInterfaces returns a wrapped version of the embedded ResponseWriter // and selectively implements the same combination of additional interfaces as // the wrapped one. The interfaces it may implement are: http.Hijacker, -// http.CloseNotifier, http.Pusher, http.Flusher and io.ReaderFrom. The standard +// http.Pusher, http.Flusher and io.ReaderFrom. The standard // library is known to assert the existence of these interfaces and behaves // differently. This implementation is derived from // https://github.com/felixge/httpsnoop. func (w *interceptingWriter) reimplementInterfaces() http.ResponseWriter { var ( - hj, i0 = w.ResponseWriter.(http.Hijacker) - cn, i1 = w.ResponseWriter.(http.CloseNotifier) + hj, i1 = w.ResponseWriter.(http.Hijacker) pu, i2 = w.ResponseWriter.(http.Pusher) fl, i3 = w.ResponseWriter.(http.Flusher) rf, i4 = w.ResponseWriter.(io.ReaderFrom) ) switch { - case !i0 && !i1 && !i2 && !i3 && !i4: + case !i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter }{w} - case !i0 && !i1 && !i2 && !i3 && i4: + case !i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter io.ReaderFrom }{w, rf} - case !i0 && !i1 && !i2 && i3 && !i4: + case !i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Flusher }{w, fl} - case !i0 && !i1 && !i2 && i3 && i4: + case !i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Flusher io.ReaderFrom }{w, fl, rf} - case !i0 && !i1 && i2 && !i3 && !i4: + case !i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Pusher }{w, pu} - case !i0 && !i1 && i2 && !i3 && i4: + case !i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Pusher io.ReaderFrom }{w, pu, rf} - case !i0 && !i1 && i2 && i3 && !i4: + case !i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Pusher http.Flusher }{w, pu, fl} - case !i0 && !i1 && i2 && i3 && i4: + case !i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Pusher http.Flusher io.ReaderFrom }{w, pu, fl, rf} - case !i0 && i1 && !i2 && !i3 && !i4: - return struct { - http.ResponseWriter - http.CloseNotifier - }{w, cn} - case !i0 && i1 && !i2 && !i3 && i4: - return struct { - http.ResponseWriter - http.CloseNotifier - io.ReaderFrom - }{w, cn, rf} - case !i0 && i1 && !i2 && i3 && !i4: - return struct { - http.ResponseWriter - http.CloseNotifier - http.Flusher - }{w, cn, fl} - case !i0 && i1 && !i2 && i3 && i4: - return struct { - http.ResponseWriter - http.CloseNotifier - http.Flusher - io.ReaderFrom - }{w, cn, fl, rf} - case !i0 && i1 && i2 && !i3 && !i4: - return struct { - http.ResponseWriter - http.CloseNotifier - http.Pusher - }{w, cn, pu} - case !i0 && i1 && i2 && !i3 && i4: - return struct { - http.ResponseWriter - http.CloseNotifier - http.Pusher - io.ReaderFrom - }{w, cn, pu, rf} - case !i0 && i1 && i2 && i3 && !i4: - return struct { - http.ResponseWriter - http.CloseNotifier - http.Pusher - http.Flusher - }{w, cn, pu, fl} - case !i0 && i1 && i2 && i3 && i4: - return struct { - http.ResponseWriter - http.CloseNotifier - http.Pusher - http.Flusher - io.ReaderFrom - }{w, cn, pu, fl, rf} - case i0 && !i1 && !i2 && !i3 && !i4: + case i1 && !i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker }{w, hj} - case i0 && !i1 && !i2 && !i3 && i4: + case i1 && !i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker io.ReaderFrom }{w, hj, rf} - case i0 && !i1 && !i2 && i3 && !i4: + case i1 && !i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Flusher }{w, hj, fl} - case i0 && !i1 && !i2 && i3 && i4: + case i1 && !i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Flusher io.ReaderFrom }{w, hj, fl, rf} - case i0 && !i1 && i2 && !i3 && !i4: + case i1 && i2 && !i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher }{w, hj, pu} - case i0 && !i1 && i2 && !i3 && i4: + case i1 && i2 && !i3 && i4: return struct { http.ResponseWriter http.Hijacker http.Pusher io.ReaderFrom }{w, hj, pu, rf} - case i0 && !i1 && i2 && i3 && !i4: + case i1 && i2 && i3 && !i4: return struct { http.ResponseWriter http.Hijacker http.Pusher http.Flusher }{w, hj, pu, fl} - case i0 && !i1 && i2 && i3 && i4: + case i1 && i2 && i3 && i4: return struct { http.ResponseWriter http.Hijacker @@ -189,66 +136,6 @@ func (w *interceptingWriter) reimplementInterfaces() http.ResponseWriter { http.Flusher io.ReaderFrom }{w, hj, pu, fl, rf} - case i0 && i1 && !i2 && !i3 && !i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - }{w, hj, cn} - case i0 && i1 && !i2 && !i3 && i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - io.ReaderFrom - }{w, hj, cn, rf} - case i0 && i1 && !i2 && i3 && !i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - http.Flusher - }{w, hj, cn, fl} - case i0 && i1 && !i2 && i3 && i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - http.Flusher - io.ReaderFrom - }{w, hj, cn, fl, rf} - case i0 && i1 && i2 && !i3 && !i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - http.Pusher - }{w, hj, cn, pu} - case i0 && i1 && i2 && !i3 && i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - http.Pusher - io.ReaderFrom - }{w, hj, cn, pu, rf} - case i0 && i1 && i2 && i3 && !i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - http.Pusher - http.Flusher - }{w, hj, cn, pu, fl} - case i0 && i1 && i2 && i3 && i4: - return struct { - http.ResponseWriter - http.Hijacker - http.CloseNotifier - http.Pusher - http.Flusher - io.ReaderFrom - }{w, hj, cn, pu, fl, rf} default: return struct { http.ResponseWriter diff --git a/transport/http/intercepting_writer_test.go b/transport/http/intercepting_writer_test.go index ad4a070..81a6efc 100644 --- a/transport/http/intercepting_writer_test.go +++ b/transport/http/intercepting_writer_test.go @@ -1,3 +1,5 @@ +//go:build unit + package http import ( @@ -10,11 +12,10 @@ import ( type versatileWriter struct { http.ResponseWriter - closeNotifyCalled bool - hijackCalled bool - readFromCalled bool - pushCalled bool - flushCalled bool + hijackCalled bool + readFromCalled bool + pushCalled bool + flushCalled bool } func (v *versatileWriter) Flush() { v.flushCalled = true } @@ -22,11 +23,12 @@ func (v *versatileWriter) Push(target string, opts *http.PushOptions) error { v.pushCalled = true return nil } + func (v *versatileWriter) ReadFrom(r io.Reader) (n int64, err error) { v.readFromCalled = true return 0, nil } -func (v *versatileWriter) CloseNotify() <-chan bool { v.closeNotifyCalled = true; return nil } + func (v *versatileWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) { v.hijackCalled = true return nil, nil, nil @@ -37,7 +39,6 @@ func TestInterceptingWriter_passthroughs(t *testing.T) { iw := (&interceptingWriter{ResponseWriter: w}).reimplementInterfaces() iw.(http.Flusher).Flush() iw.(http.Pusher).Push("", nil) - iw.(http.CloseNotifier).CloseNotify() iw.(http.Hijacker).Hijack() iw.(io.ReaderFrom).ReadFrom(nil) @@ -47,9 +48,6 @@ func TestInterceptingWriter_passthroughs(t *testing.T) { if !w.pushCalled { t.Error("Push not called") } - if !w.closeNotifyCalled { - t.Error("CloseNotify not called") - } if !w.hijackCalled { t.Error("Hijack not called") } @@ -61,979 +59,136 @@ func TestInterceptingWriter_passthroughs(t *testing.T) { // TestInterceptingWriter_reimplementInterfaces is also derived from // https://github.com/felixge/httpsnoop, like interceptingWriter. func TestInterceptingWriter_reimplementInterfaces(t *testing.T) { - // combination 1/32 - { - t.Log("http.ResponseWriter") - inner := struct { - http.ResponseWriter - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 2/32 - { - t.Log("http.ResponseWriter, http.Pusher") - inner := struct { - http.ResponseWriter - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 3/32 - { - t.Log("http.ResponseWriter, io.ReaderFrom") - inner := struct { - http.ResponseWriter - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 4/32 - { - t.Log("http.ResponseWriter, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 5/32 - { - t.Log("http.ResponseWriter, http.Hijacker") - inner := struct { - http.ResponseWriter - http.Hijacker - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 6/32 - { - t.Log("http.ResponseWriter, http.Hijacker, http.Pusher") - inner := struct { - http.ResponseWriter - http.Hijacker - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 7/32 - { - t.Log("http.ResponseWriter, http.Hijacker, io.ReaderFrom") - inner := struct { - http.ResponseWriter - http.Hijacker - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 8/32 - { - t.Log("http.ResponseWriter, http.Hijacker, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - http.Hijacker - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 9/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier") - inner := struct { - http.ResponseWriter - http.CloseNotifier - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 10/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier, http.Pusher") - inner := struct { - http.ResponseWriter - http.CloseNotifier - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 11/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier, io.ReaderFrom") - inner := struct { - http.ResponseWriter - http.CloseNotifier - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 12/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - http.CloseNotifier - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 13/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker") - inner := struct { - http.ResponseWriter - http.CloseNotifier - http.Hijacker - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 14/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, http.Pusher") - inner := struct { - http.ResponseWriter - http.CloseNotifier - http.Hijacker - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 15/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, io.ReaderFrom") - inner := struct { - http.ResponseWriter - http.CloseNotifier - http.Hijacker - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 16/32 - { - t.Log("http.ResponseWriter, http.CloseNotifier, http.Hijacker, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - http.CloseNotifier - http.Hijacker - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 17/32 - { - t.Log("http.ResponseWriter, http.Flusher") - inner := struct { - http.ResponseWriter - http.Flusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 18/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 19/32 - { - t.Log("http.ResponseWriter, http.Flusher, io.ReaderFrom") - inner := struct { - http.ResponseWriter - http.Flusher - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 20/32 - { - t.Log("http.ResponseWriter, http.Flusher, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 21/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.Hijacker") - inner := struct { - http.ResponseWriter - http.Flusher - http.Hijacker - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 22/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - http.Hijacker - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 23/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom") - inner := struct { - http.ResponseWriter - http.Flusher - http.Hijacker - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 24/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - http.Hijacker - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 25/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 26/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 27/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, io.ReaderFrom") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 28/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 29/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - http.Hijacker - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 30/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - http.Hijacker - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != false { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } - - // combination 31/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, io.ReaderFrom") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - http.Hijacker - io.ReaderFrom - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != false { - t.Error("unexpected interface") - } - - } - - // combination 32/32 - { - t.Log("http.ResponseWriter, http.Flusher, http.CloseNotifier, http.Hijacker, io.ReaderFrom, http.Pusher") - inner := struct { - http.ResponseWriter - http.Flusher - http.CloseNotifier - http.Hijacker - io.ReaderFrom - http.Pusher - }{} - w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() - if _, ok := w.(http.ResponseWriter); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Flusher); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.CloseNotifier); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Hijacker); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(io.ReaderFrom); ok != true { - t.Error("unexpected interface") - } - if _, ok := w.(http.Pusher); ok != true { - t.Error("unexpected interface") - } - - } + doTest := func(t *testing.T, name string, inner http.ResponseWriter, flusher, hijacker, readerFrom, pusher bool) { + t.Run(name, func(t *testing.T) { + w := (&interceptingWriter{ResponseWriter: inner}).reimplementInterfaces() + if _, ok := w.(http.ResponseWriter); ok != true { + t.Error("unexpected interface") + } + if _, ok := w.(http.Flusher); ok != flusher { + t.Error("unexpected interface") + } + if _, ok := w.(http.Hijacker); ok != hijacker { + t.Error("unexpected interface") + } + if _, ok := w.(io.ReaderFrom); ok != readerFrom { + t.Error("unexpected interface") + } + if _, ok := w.(http.Pusher); ok != pusher { + t.Error("unexpected interface") + } + }) + } + + // combination 1/16 + doTest(t, "http.ResponseWriter", struct { + http.ResponseWriter + }{}, false, false, false, false) + + // combination 2/16 + doTest(t, "http.ResponseWriter, http.Pusher", struct { + http.ResponseWriter + http.Pusher + }{}, false, false, false, true) + + // combination 3/16 + doTest(t, "http.ResponseWriter, io.ReaderFrom", struct { + http.ResponseWriter + io.ReaderFrom + }{}, false, false, true, false) + + // combination 4/16 + doTest(t, "http.ResponseWriter, io.ReaderFrom, http.Pusher", struct { + http.ResponseWriter + io.ReaderFrom + http.Pusher + }{}, false, false, true, true) + + // combination 5/16 + doTest(t, "http.ResponseWriter, http.Hijacker", struct { + http.ResponseWriter + http.Hijacker + }{}, false, true, false, false) + + // combination 6/16 + doTest(t, "http.ResponseWriter, http.Hijacker, http.Pusher", struct { + http.ResponseWriter + http.Hijacker + http.Pusher + }{}, false, true, false, true) + + // combination 7/16 + doTest(t, "http.ResponseWriter, http.Hijacker, io.ReaderFrom", struct { + http.ResponseWriter + http.Hijacker + io.ReaderFrom + }{}, false, true, true, false) + + // combination 8/16 + doTest(t, "http.ResponseWriter, http.Hijacker, io.ReaderFrom, http.Pusher", struct { + http.ResponseWriter + http.Hijacker + io.ReaderFrom + http.Pusher + }{}, false, true, true, true) + + // combination 9/16 + doTest(t, "http.ResponseWriter, http.Flusher", struct { + http.ResponseWriter + http.Flusher + }{}, true, false, false, false) + + // combination 10/16 + doTest(t, "http.ResponseWriter, http.Flusher, http.Pusher", struct { + http.ResponseWriter + http.Flusher + http.Pusher + }{}, true, false, false, true) + + // combination 11/16 + doTest(t, "http.ResponseWriter, http.Flusher, io.ReaderFrom", struct { + http.ResponseWriter + http.Flusher + io.ReaderFrom + }{}, true, false, true, false) + + // combination 12/16 + doTest(t, "http.ResponseWriter, http.Flusher, io.ReaderFrom, http.Pusher", struct { + http.ResponseWriter + http.Flusher + io.ReaderFrom + http.Pusher + }{}, true, false, true, true) + + // combination 13/16 + doTest(t, "http.ResponseWriter, http.Flusher, http.Hijacker", struct { + http.ResponseWriter + http.Flusher + http.Hijacker + }{}, true, true, false, false) + + // combination 14/16 + doTest(t, "http.ResponseWriter, http.Flusher, http.Hijacker, http.Pusher", struct { + http.ResponseWriter + http.Flusher + http.Hijacker + http.Pusher + }{}, true, true, false, true) + + // combination 15/16 + doTest(t, "http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom", struct { + http.ResponseWriter + http.Flusher + http.Hijacker + io.ReaderFrom + }{}, true, true, true, false) + + // combination 16/16 + doTest(t, "http.ResponseWriter, http.Flusher, http.Hijacker, io.ReaderFrom, http.Pusher", struct { + http.ResponseWriter + http.Flusher + http.Hijacker + io.ReaderFrom + http.Pusher + }{}, true, true, true, true) } diff --git a/transport/http/request_response_funcs_test.go b/transport/http/request_response_funcs_test.go index e003fc9..bdb782b 100644 --- a/transport/http/request_response_funcs_test.go +++ b/transport/http/request_response_funcs_test.go @@ -1,3 +1,5 @@ +//go:build unit + package http_test import ( @@ -5,7 +7,7 @@ import ( "net/http/httptest" "testing" - httptransport "github.com/go-kit/kit/transport/http" + httptransport "github.com/kikihakiem/gkit/transport/http" ) func TestSetHeader(t *testing.T) { diff --git a/transport/http/server.go b/transport/http/server.go index ab87d4a..2ad802c 100644 --- a/transport/http/server.go +++ b/transport/http/server.go @@ -5,37 +5,35 @@ import ( "encoding/json" "net/http" - "github.com/go-kit/kit/endpoint" - "github.com/go-kit/kit/transport" - "github.com/go-kit/log" + gkit "github.com/kikihakiem/gkit/core" ) // Server wraps an endpoint and implements http.Handler. -type Server struct { - e endpoint.Endpoint - dec DecodeRequestFunc - enc EncodeResponseFunc - before []RequestFunc - after []ServerResponseFunc - errorEncoder ErrorEncoder +type Server[Req, Res any] struct { + e gkit.Endpoint[Req, Res] + dec gkit.EncodeDecodeFunc[*http.Request, Req] + enc EncodeResponseFunc[Res] + before []gkit.BeforeRequestFunc[*http.Request] + after []gkit.AfterResponseFunc[http.ResponseWriter] + errorEncoder gkit.ErrorEncoder[http.ResponseWriter] finalizer []ServerFinalizerFunc - errorHandler transport.ErrorHandler + errorHandler gkit.ErrorHandler } // NewServer constructs a new server, which implements http.Handler and wraps // the provided endpoint. -func NewServer( - e endpoint.Endpoint, - dec DecodeRequestFunc, - enc EncodeResponseFunc, - options ...ServerOption, -) *Server { - s := &Server{ +func NewServer[Req, Res any]( + e gkit.Endpoint[Req, Res], + dec gkit.EncodeDecodeFunc[*http.Request, Req], + enc EncodeResponseFunc[Res], + options ...ServerOption[Req, Res], +) *Server[Req, Res] { + s := &Server[Req, Res]{ e: e, dec: dec, enc: enc, errorEncoder: DefaultErrorEncoder, - errorHandler: transport.NewLogErrorHandler(log.NewNopLogger()), + errorHandler: gkit.LogErrorHandler(nil), } for _, option := range options { option(s) @@ -44,36 +42,26 @@ func NewServer( } // ServerOption sets an optional parameter for servers. -type ServerOption func(*Server) +type ServerOption[Req, Res any] gkit.Option[*Server[Req, Res]] // ServerBefore functions are executed on the HTTP request object before the // request is decoded. -func ServerBefore(before ...RequestFunc) ServerOption { - return func(s *Server) { s.before = append(s.before, before...) } +func ServerBefore[Req, Res any](before ...gkit.BeforeRequestFunc[*http.Request]) ServerOption[Req, Res] { + return func(s *Server[Req, Res]) { s.before = append(s.before, before...) } } // ServerAfter functions are executed on the HTTP response writer after the // endpoint is invoked, but before anything is written to the client. -func ServerAfter(after ...ServerResponseFunc) ServerOption { - return func(s *Server) { s.after = append(s.after, after...) } +func ServerAfter[Req, Res any](after ...gkit.AfterResponseFunc[http.ResponseWriter]) ServerOption[Req, Res] { + return func(s *Server[Req, Res]) { s.after = append(s.after, after...) } } // ServerErrorEncoder is used to encode errors to the http.ResponseWriter // whenever they're encountered in the processing of a request. Clients can // use this to provide custom error formatting and response codes. By default, // errors will be written with the DefaultErrorEncoder. -func ServerErrorEncoder(ee ErrorEncoder) ServerOption { - return func(s *Server) { s.errorEncoder = ee } -} - -// ServerErrorLogger is used to log non-terminal errors. By default, no errors -// are logged. This is intended as a diagnostic measure. Finer-grained control -// of error handling, including logging in more detail, should be performed in a -// custom ServerErrorEncoder or ServerFinalizer, both of which have access to -// the context. -// Deprecated: Use ServerErrorHandler instead. -func ServerErrorLogger(logger log.Logger) ServerOption { - return func(s *Server) { s.errorHandler = transport.NewLogErrorHandler(logger) } +func ServerErrorEncoder[Req, Res any](ee gkit.ErrorEncoder[http.ResponseWriter]) ServerOption[Req, Res] { + return func(s *Server[Req, Res]) { s.errorEncoder = ee } } // ServerErrorHandler is used to handle non-terminal errors. By default, non-terminal errors @@ -81,18 +69,18 @@ func ServerErrorLogger(logger log.Logger) ServerOption { // of error handling, including logging in more detail, should be performed in a // custom ServerErrorEncoder or ServerFinalizer, both of which have access to // the context. -func ServerErrorHandler(errorHandler transport.ErrorHandler) ServerOption { - return func(s *Server) { s.errorHandler = errorHandler } +func ServerErrorHandler[Req, Res any](errorHandler gkit.ErrorHandler) ServerOption[Req, Res] { + return func(s *Server[Req, Res]) { s.errorHandler = errorHandler } } // ServerFinalizer is executed at the end of every HTTP request. // By default, no finalizer is registered. -func ServerFinalizer(f ...ServerFinalizerFunc) ServerOption { - return func(s *Server) { s.finalizer = append(s.finalizer, f...) } +func ServerFinalizer[Req, Res any](f ...ServerFinalizerFunc) ServerOption[Req, Res] { + return func(s *Server[Req, Res]) { s.finalizer = append(s.finalizer, f...) } } // ServeHTTP implements http.Handler. -func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (s Server[Req, Res]) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx := r.Context() if len(s.finalizer) > 0 { @@ -114,24 +102,24 @@ func (s Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { request, err := s.dec(ctx, r) if err != nil { s.errorHandler.Handle(ctx, err) - s.errorEncoder(ctx, err, w) + s.errorEncoder(ctx, w, err) return } response, err := s.e(ctx, request) if err != nil { s.errorHandler.Handle(ctx, err) - s.errorEncoder(ctx, err, w) + s.errorEncoder(ctx, w, err) return } for _, f := range s.after { - ctx = f(ctx, w) + ctx = f(ctx, w, err) } if err := s.enc(ctx, w, response); err != nil { s.errorHandler.Handle(ctx, err) - s.errorEncoder(ctx, err, w) + s.errorEncoder(ctx, w, err) return } } @@ -187,7 +175,7 @@ func EncodeJSONResponse(_ context.Context, w http.ResponseWriter, response inter // the marshaling succeeds, a content type of application/json and the JSON // encoded form of the error will be used. If the error implements StatusCoder, // the provided StatusCode will be used instead of 500. -func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) { +func DefaultErrorEncoder(_ context.Context, w http.ResponseWriter, err error) { contentType, body := "text/plain; charset=utf-8", []byte(err.Error()) if marshaler, ok := err.(json.Marshaler); ok { if jsonBody, marshalErr := marshaler.MarshalJSON(); marshalErr == nil { @@ -207,7 +195,7 @@ func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter) { code = sc.StatusCode() } w.WriteHeader(code) - w.Write(body) + w.Write(body) //nolint:errcheck } // StatusCoder is checked by DefaultErrorEncoder. If an error value implements diff --git a/transport/http/server_test.go b/transport/http/server_test.go index 5c0fadb..d9ce74f 100644 --- a/transport/http/server_test.go +++ b/transport/http/server_test.go @@ -3,21 +3,23 @@ package http_test import ( "context" "errors" - "io/ioutil" + "io" "net/http" "net/http/httptest" "strings" "testing" "time" - "github.com/go-kit/kit/endpoint" - httptransport "github.com/go-kit/kit/transport/http" + gkit "github.com/kikihakiem/gkit/core" + httptransport "github.com/kikihakiem/gkit/transport/http" ) +type emptyStruct struct{} + func TestServerBadDecode(t *testing.T) { handler := httptransport.NewServer( - func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, errors.New("dang") }, + func(context.Context, interface{}) (interface{}, error) { return emptyStruct{}, nil }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, errors.New("dang") }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, ) server := httptest.NewServer(handler) @@ -30,8 +32,8 @@ func TestServerBadDecode(t *testing.T) { func TestServerBadEndpoint(t *testing.T) { handler := httptransport.NewServer( - func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("dang") }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, + func(context.Context, interface{}) (interface{}, error) { return emptyStruct{}, errors.New("dang") }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return nil }, ) server := httptest.NewServer(handler) @@ -44,8 +46,8 @@ func TestServerBadEndpoint(t *testing.T) { func TestServerBadEncode(t *testing.T) { handler := httptransport.NewServer( - func(context.Context, interface{}) (interface{}, error) { return struct{}{}, nil }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, + func(context.Context, interface{}) (interface{}, error) { return emptyStruct{}, nil }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, nil }, func(context.Context, http.ResponseWriter, interface{}) error { return errors.New("dang") }, ) server := httptest.NewServer(handler) @@ -64,11 +66,11 @@ func TestServerErrorEncoder(t *testing.T) { } return http.StatusInternalServerError } - handler := httptransport.NewServer( - func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, - func(context.Context, http.ResponseWriter, interface{}) error { return nil }, - httptransport.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }), + handler := httptransport.NewServer[emptyStruct, emptyStruct]( + func(context.Context, emptyStruct) (emptyStruct, error) { return emptyStruct{}, errTeapot }, + func(context.Context, *http.Request) (emptyStruct, error) { return emptyStruct{}, nil }, + func(context.Context, http.ResponseWriter, emptyStruct) error { return nil }, + httptransport.ServerErrorEncoder[emptyStruct, emptyStruct](func(_ context.Context, w http.ResponseWriter, err error) { w.WriteHeader(code(err)) }), ) server := httptest.NewServer(handler) defer server.Close() @@ -78,12 +80,31 @@ func TestServerErrorEncoder(t *testing.T) { } } +func TestServerErrorHandler(t *testing.T) { + errTeapot := errors.New("teapot") + msgChan := make(chan string, 1) + handler := httptransport.NewServer[emptyStruct, emptyStruct]( + func(context.Context, emptyStruct) (emptyStruct, error) { return emptyStruct{}, errTeapot }, + func(context.Context, *http.Request) (emptyStruct, error) { return emptyStruct{}, nil }, + func(context.Context, http.ResponseWriter, emptyStruct) error { return nil }, + httptransport.ServerErrorHandler[emptyStruct, emptyStruct](gkit.ErrorHandlerFunc(func(ctx context.Context, err error) { + msgChan <- err.Error() + })), + ) + server := httptest.NewServer(handler) + defer server.Close() + http.Get(server.URL) + if want, have := errTeapot.Error(), <-msgChan; want != have { + t.Errorf("want %s, have %s", want, have) + } +} + func TestServerHappyPath(t *testing.T) { step, response := testServer(t) step() resp := <-response defer resp.Body.Close() - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) if want, have := http.StatusOK, resp.StatusCode; want != have { t.Errorf("want %d, have %d (%s)", want, have, buf) } @@ -95,25 +116,25 @@ func TestMultipleServerBefore(t *testing.T) { headerVal = "Helllo you stinky lizard" statusCode = http.StatusTeapot responseBody = "go eat a fly ugly\n" - done = make(chan struct{}) + done = make(chan emptyStruct) ) - handler := httptransport.NewServer( - endpoint.Nop, - func(context.Context, *http.Request) (interface{}, error) { - return struct{}{}, nil + handler := httptransport.NewServer[emptyStruct, emptyStruct]( + gkit.NopEndpoint, + func(context.Context, *http.Request) (emptyStruct, error) { + return emptyStruct{}, nil }, - func(_ context.Context, w http.ResponseWriter, _ interface{}) error { + func(_ context.Context, w http.ResponseWriter, _ emptyStruct) error { w.Header().Set(headerKey, headerVal) w.WriteHeader(statusCode) w.Write([]byte(responseBody)) return nil }, - httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { + httptransport.ServerBefore[emptyStruct, emptyStruct](func(ctx context.Context, r *http.Request) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), - httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { + httptransport.ServerBefore[emptyStruct, emptyStruct](func(ctx context.Context, r *http.Request) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerBefores are used") } @@ -140,25 +161,25 @@ func TestMultipleServerAfter(t *testing.T) { headerVal = "Helllo you stinky lizard" statusCode = http.StatusTeapot responseBody = "go eat a fly ugly\n" - done = make(chan struct{}) + done = make(chan emptyStruct) ) - handler := httptransport.NewServer( - endpoint.Nop, - func(context.Context, *http.Request) (interface{}, error) { - return struct{}{}, nil + handler := httptransport.NewServer[emptyStruct, emptyStruct]( + gkit.NopEndpoint, + func(context.Context, *http.Request) (emptyStruct, error) { + return emptyStruct{}, nil }, - func(_ context.Context, w http.ResponseWriter, _ interface{}) error { + func(_ context.Context, w http.ResponseWriter, _ emptyStruct) error { w.Header().Set(headerKey, headerVal) w.WriteHeader(statusCode) w.Write([]byte(responseBody)) return nil }, - httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { + httptransport.ServerAfter[emptyStruct, emptyStruct](func(ctx context.Context, _ http.ResponseWriter, _ error) context.Context { ctx = context.WithValue(ctx, "one", 1) return ctx }), - httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { + httptransport.ServerAfter[emptyStruct, emptyStruct](func(ctx context.Context, _ http.ResponseWriter, _ error) context.Context { if _, ok := ctx.Value("one").(int); !ok { t.Error("Value was not set properly when multiple ServerAfters are used") } @@ -185,20 +206,20 @@ func TestServerFinalizer(t *testing.T) { headerVal = "Helllo you stinky lizard" statusCode = http.StatusTeapot responseBody = "go eat a fly ugly\n" - done = make(chan struct{}) + done = make(chan emptyStruct) ) - handler := httptransport.NewServer( - endpoint.Nop, - func(context.Context, *http.Request) (interface{}, error) { - return struct{}{}, nil + handler := httptransport.NewServer[emptyStruct, emptyStruct]( + gkit.NopEndpoint, + func(context.Context, *http.Request) (emptyStruct, error) { + return emptyStruct{}, nil }, - func(_ context.Context, w http.ResponseWriter, _ interface{}) error { + func(_ context.Context, w http.ResponseWriter, _ emptyStruct) error { w.Header().Set(headerKey, headerVal) w.WriteHeader(statusCode) w.Write([]byte(responseBody)) return nil }, - httptransport.ServerFinalizer(func(ctx context.Context, code int, _ *http.Request) { + httptransport.ServerFinalizer[emptyStruct, emptyStruct](func(ctx context.Context, code int, _ *http.Request) { if want, have := statusCode, code; want != have { t.Errorf("StatusCode: want %d, have %d", want, have) } @@ -238,7 +259,7 @@ func (e enhancedResponse) Headers() http.Header { return http.Header{"X-Edward": func TestEncodeJSONResponse(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return enhancedResponse{Foo: "bar"}, nil }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, nil }, httptransport.EncodeJSONResponse, ) @@ -255,13 +276,13 @@ func TestEncodeJSONResponse(t *testing.T) { if want, have := "Snowden", resp.Header.Get("X-Edward"); want != have { t.Errorf("X-Edward: want %q, have %q", want, have) } - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) if want, have := `{"foo":"bar"}`, strings.TrimSpace(string(buf)); want != have { t.Errorf("Body: want %s, have %s", want, have) } } -type multiHeaderResponse struct{} +type multiHeaderResponse emptyStruct func (_ multiHeaderResponse) Headers() http.Header { return http.Header{"Vary": []string{"Origin", "User-Agent"}} @@ -270,7 +291,7 @@ func (_ multiHeaderResponse) Headers() http.Header { func TestAddMultipleHeaders(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return multiHeaderResponse{}, nil }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, nil }, httptransport.EncodeJSONResponse, ) @@ -281,7 +302,7 @@ func TestAddMultipleHeaders(t *testing.T) { if err != nil { t.Fatal(err) } - expect := map[string]map[string]struct{}{"Vary": map[string]struct{}{"Origin": struct{}{}, "User-Agent": struct{}{}}} + expect := map[string]map[string]emptyStruct{"Vary": {"Origin": emptyStruct{}, "User-Agent": emptyStruct{}}} for k, vls := range resp.Header { for _, v := range vls { delete((expect[k]), v) @@ -307,7 +328,7 @@ func TestAddMultipleHeadersErrorEncoder(t *testing.T) { func(context.Context, interface{}) (interface{}, error) { return nil, multiHeaderResponseError{msg: errStr} }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, nil }, httptransport.EncodeJSONResponse, ) @@ -318,7 +339,7 @@ func TestAddMultipleHeadersErrorEncoder(t *testing.T) { if err != nil { t.Fatal(err) } - expect := map[string]map[string]struct{}{"Vary": map[string]struct{}{"Origin": struct{}{}, "User-Agent": struct{}{}}} + expect := map[string]map[string]emptyStruct{"Vary": {"Origin": emptyStruct{}, "User-Agent": emptyStruct{}}} for k, vls := range resp.Header { for _, v := range vls { delete((expect[k]), v) @@ -327,19 +348,19 @@ func TestAddMultipleHeadersErrorEncoder(t *testing.T) { t.Errorf("Header: unexpected header %s: %v", k, expect[k]) } } - if b, _ := ioutil.ReadAll(resp.Body); errStr != string(b) { + if b, _ := io.ReadAll(resp.Body); errStr != string(b) { t.Errorf("ErrorEncoder: got: %q, expected: %q", b, errStr) } } -type noContentResponse struct{} +type noContentResponse emptyStruct func (e noContentResponse) StatusCode() int { return http.StatusNoContent } func TestEncodeNoContent(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return noContentResponse{}, nil }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, nil }, httptransport.EncodeJSONResponse, ) @@ -353,13 +374,13 @@ func TestEncodeNoContent(t *testing.T) { if want, have := http.StatusNoContent, resp.StatusCode; want != have { t.Errorf("StatusCode: want %d, have %d", want, have) } - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) if want, have := 0, len(buf); want != have { t.Errorf("Body: want no content, have %d bytes", have) } } -type enhancedError struct{} +type enhancedError emptyStruct func (e enhancedError) Error() string { return "enhanced error" } func (e enhancedError) StatusCode() int { return http.StatusTeapot } @@ -369,7 +390,7 @@ func (e enhancedError) Headers() http.Header { return http.Header{"X-Enh func TestEnhancedError(t *testing.T) { handler := httptransport.NewServer( func(context.Context, interface{}) (interface{}, error) { return nil, enhancedError{} }, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, + func(context.Context, *http.Request) (interface{}, error) { return emptyStruct{}, nil }, func(_ context.Context, w http.ResponseWriter, _ interface{}) error { return nil }, ) @@ -387,7 +408,7 @@ func TestEnhancedError(t *testing.T) { if want, have := "1", resp.Header.Get("X-Enhanced"); want != have { t.Errorf("X-Enhanced: want %q, have %q", want, have) } - buf, _ := ioutil.ReadAll(resp.Body) + buf, _ := io.ReadAll(resp.Body) if want, have := `{"err":"enhanced"}`, strings.TrimSpace(string(buf)); want != have { t.Errorf("Body: want %s, have %s", want, have) } @@ -418,14 +439,14 @@ func TestNoOpRequestDecoder(t *testing.T) { func testServer(t *testing.T) (step func(), resp <-chan *http.Response) { var ( stepch = make(chan bool) - endpoint = func(context.Context, interface{}) (interface{}, error) { <-stepch; return struct{}{}, nil } + endpoint = func(context.Context, emptyStruct) (emptyStruct, error) { <-stepch; return emptyStruct{}, nil } response = make(chan *http.Response) - handler = httptransport.NewServer( + handler = httptransport.NewServer[emptyStruct, emptyStruct]( endpoint, - func(context.Context, *http.Request) (interface{}, error) { return struct{}{}, nil }, - func(context.Context, http.ResponseWriter, interface{}) error { return nil }, - httptransport.ServerBefore(func(ctx context.Context, r *http.Request) context.Context { return ctx }), - httptransport.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context { return ctx }), + func(context.Context, *http.Request) (emptyStruct, error) { return emptyStruct{}, nil }, + func(context.Context, http.ResponseWriter, emptyStruct) error { return nil }, + httptransport.ServerBefore[emptyStruct, emptyStruct](func(ctx context.Context, _ *http.Request) context.Context { return ctx }), + httptransport.ServerAfter[emptyStruct, emptyStruct](func(ctx context.Context, _ http.ResponseWriter, _ error) context.Context { return ctx }), ) ) go func() {