go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gce/appengine/testing/roundtripper/roundtripper.go (about) 1 // Copyright 2018 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package roundtripper contains http.RoundTripper implementations suitable for 16 // testing. 17 package roundtripper 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "io" 23 "net/http" 24 "reflect" 25 "strings" 26 27 "go.chromium.org/luci/common/errors" 28 ) 29 30 // Ensure *StringRoundTripper implements http.RoundTripper. 31 var _ http.RoundTripper = &StringRoundTripper{} 32 33 // StringRoundTripper implements http.RoundTripper to handle *http.Requests. 34 type StringRoundTripper struct { 35 // Handler is called by RoundTrip. 36 // Returns an HTTP status code and a string to return in an *http.Response. 37 Handler func(*http.Request) (int, string) 38 } 39 40 // RoundTrip handles an *http.Request, returning an *http.Response with a string 41 // body. Panics on error. Implements http.RoundTripper. 42 func (t *StringRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 43 if t.Handler == nil { 44 panic("handler is required") 45 } 46 code, rsp := t.Handler(req) 47 return &http.Response{ 48 Body: io.NopCloser(strings.NewReader(rsp)), 49 StatusCode: code, 50 }, nil 51 } 52 53 // Ensure *JSONRoundTripper implements http.RoundTripper. 54 var _ http.RoundTripper = &JSONRoundTripper{} 55 56 // JSONRoundTripper implements http.RoundTripper to handle *http.Requests with a 57 // JSON body. 58 type JSONRoundTripper struct { 59 // Handler is called by RoundTrip with the unmarshalled JSON from an *http.Request. 60 // Returns an HTTP status code and an any to marshal as JSON in an *http.Response. 61 Handler func(any) (int, any) 62 // Type is the reflect.Type to unmarshal *http.Request.Body into. 63 // Defaults to map[string]string{}. 64 Type reflect.Type 65 } 66 67 // RoundTrip handles an *http.Request with a JSON body, returning an 68 // *http.Response with a JSON body. Panics on error. Implements 69 // http.RoundTripper. 70 func (t *JSONRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { 71 // Unmarshal *http.Request.Body. 72 if t.Type == nil { 73 t.Type = reflect.TypeOf(map[string]string{}) 74 } 75 val := reflect.New(t.Type).Interface() 76 if req.Body != nil { 77 err := json.NewDecoder(req.Body).Decode(&val) 78 req.Body.Close() 79 if err != nil { 80 panic(errors.Annotate(err, "failed to unmarshal *http.Request.Body").Err()) 81 } 82 } 83 84 // Marshal the returned value from t.Handler into *http.Response.Body. 85 if t.Handler == nil { 86 panic("handler is required") 87 } 88 code, rsp := t.Handler(val) 89 b, err := json.Marshal(rsp) 90 if err != nil { 91 panic(errors.Annotate(err, "failed to marshal *http.Response.Body").Err()) 92 } 93 return &http.Response{ 94 Body: io.NopCloser(bytes.NewReader(b)), 95 StatusCode: code, 96 }, nil 97 }