diff --git a/README.md b/README.md index 89483b9..4f95e5e 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ func GetRepoInfo(user, repo string) (GithubRepoInfo, error) { call := withttp.NewCall[GithubRepoInfo](withttp.WithFasthttp()). WithURL(fmt.Sprintf("https://api.github.com/repos/%s/%s", user, repo)). WithMethod(http.MethodGet). - WithHeader("User-Agent", "withttp/0.1.0 See https://github.com/sonirico/withttp", false). + WithHeader("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false). WithParseJSON(). WithExpectedStatusCodes(http.StatusOK) @@ -186,7 +186,7 @@ func GetRepoInfo(user, repo string) (GithubRepoInfo, error) { call := withttp.NewCall[GithubRepoInfo](withttp.WithFasthttp()). WithURI(fmt.Sprintf("repos/%s/%s", user, repo)). WithMethod(http.MethodGet). - WithHeader("User-Agent", "withttp/0.1.0 See https://github.com/sonirico/withttp", false). + WithHeader("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false). WithHeaderFunc(func() (key, value string, override bool) { key = "X-Date" value = time.Now().String() @@ -283,7 +283,7 @@ func main() { call := withttp.NewCall[Order](withttp.WithFasthttp()). WithURL("https://github.com/"). WithMethod(http.MethodGet). - WithHeader("User-Agent", "withttp/0.1.0 See https://github.com/sonirico/withttp", false). + WithHeader("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false). WithParseJSONEachRowChan(res). WithExpectedStatusCodes(http.StatusOK) diff --git a/adapter_fasthttp.go b/adapter_fasthttp.go index 2561808..88642cc 100644 --- a/adapter_fasthttp.go +++ b/adapter_fasthttp.go @@ -35,11 +35,17 @@ func (a *fastHttpReqAdapter) AddHeader(key, value string) { a.req.Header.Add(key, value) } +func (a *fastHttpReqAdapter) RangeHeaders(fn func(string, string)) { + a.req.Header.VisitAll(func(key, value []byte) { + fn(string(key), string(value)) + }) +} + func (a *fastHttpReqAdapter) SetHeader(key, value string) { a.req.Header.Set(key, value) } -func (a *fastHttpReqAdapter) GetHeader(key string) (string, bool) { +func (a *fastHttpReqAdapter) Header(key string) (string, bool) { data := a.req.Header.Peek(key) if data == nil || len(data) == 0 { return "", false @@ -49,6 +55,10 @@ func (a *fastHttpReqAdapter) GetHeader(key string) (string, bool) { return string(bts), true } +func (a *fastHttpReqAdapter) Method() string { + return string(a.req.Header.Method()) +} + func (a *fastHttpReqAdapter) SetMethod(method string) { a.req.Header.SetMethod(method) } @@ -171,7 +181,7 @@ func (a *fastHttpResAdapter) SetHeader(key, value string) { a.res.Header.Set(key, value) } -func (a *fastHttpResAdapter) GetHeader(key string) (string, bool) { +func (a *fastHttpResAdapter) Header(key string) (string, bool) { data := a.res.Header.Peek(key) if data == nil || len(data) == 0 { return "", false @@ -180,3 +190,9 @@ func (a *fastHttpResAdapter) GetHeader(key string) (string, bool) { copy(bts, data) return string(bts), true } + +func (a *fastHttpResAdapter) RangeHeaders(fn func(string, string)) { + a.res.Header.VisitAll(func(key, value []byte) { + fn(string(key), string(value)) + }) +} diff --git a/adapter_native.go b/adapter_native.go index fff3342..024d73d 100644 --- a/adapter_native.go +++ b/adapter_native.go @@ -6,6 +6,7 @@ import ( "io" "net/http" "net/url" + "strings" ) type ( @@ -32,11 +33,15 @@ func (a *nativeReqAdapter) SetHeader(key, value string) { a.req.Header.Set(key, value) } -func (a *nativeReqAdapter) GetHeader(key string) (string, bool) { +func (a *nativeReqAdapter) Header(key string) (string, bool) { s := a.req.Header.Get(key) return s, len(s) > 0 } +func (a *nativeReqAdapter) Method() string { + return a.req.Method +} + func (a *nativeReqAdapter) SetMethod(method string) { a.req.Method = method } @@ -62,6 +67,12 @@ func (a *nativeReqAdapter) Body() []byte { return bts } +func (a *nativeReqAdapter) RangeHeaders(fn func(string, string)) { + for k, v := range a.req.Header { + fn(k, strings.Join(v, ", ")) + } +} + func (a *nativeReqAdapter) BodyStream() io.ReadWriteCloser { return a.body } @@ -127,7 +138,13 @@ func (a *nativeResAdapter) SetHeader(key, value string) { a.res.Header.Set(key, value) } -func (a *nativeResAdapter) GetHeader(key string) (string, bool) { +func (a *nativeResAdapter) Header(key string) (string, bool) { s := a.res.Header.Get(key) return s, len(s) > 0 } + +func (a *nativeResAdapter) RangeHeaders(fn func(string, string)) { + for k, v := range a.res.Header { + fn(k, strings.Join(v, ", ")) + } +} diff --git a/call.go b/call.go index 775ec2e..6295471 100644 --- a/call.go +++ b/call.go @@ -21,7 +21,9 @@ type ( CallResOptionFunc[T any] func(c *Call[T], res Response) error Call[T any] struct { - client client + logger logger + + client Client reqOptions []ReqOption // TODO: Linked Lists resOptions []ResOption @@ -50,7 +52,7 @@ func (f CallReqOptionFunc[T]) Configure(c *Call[T], req Request) error { return f(c, req) } -func NewCall[T any](client client) *Call[T] { +func NewCall[T any](client Client) *Call[T] { return &Call[T]{client: client} } @@ -146,6 +148,8 @@ func (c *Call[T]) callEndpoint(ctx context.Context, e *Endpoint) (err error) { wg.Wait() } + c.log("%s %s returned status code %d", req.Method(), req.URL().String(), res.Status()) + if err != nil { return } @@ -166,3 +170,11 @@ func (c *Call[T]) callEndpoint(ctx context.Context, e *Endpoint) (err error) { return } + +func (c *Call[T]) log(tpl string, args ...any) { + if c.logger == nil { + return + } + + c.logger.Printf(tpl, args...) +} diff --git a/call_with.go b/call_with.go new file mode 100644 index 0000000..19d8165 --- /dev/null +++ b/call_with.go @@ -0,0 +1,57 @@ +package withttp + +import ( + "bufio" + "fmt" + "io" +) + +func (c *Call[T]) WithLogger(l logger) *Call[T] { + c.logger = l + return c +} + +func (c *Call[T]) Log(w io.Writer) { + buf := bufio.NewWriter(w) + + _, _ = buf.WriteString(c.Req.Method()) + _, _ = buf.WriteString(" ") + _, _ = buf.WriteString(c.Req.URL().String()) + _, _ = buf.WriteString("\n") + + c.Req.RangeHeaders(func(key string, value string) { + _, _ = buf.WriteString(key) + _ = buf.WriteByte(':') + _ = buf.WriteByte(' ') + _, _ = buf.WriteString(value) + _ = buf.WriteByte('\n') + }) + + if !c.ReqIsStream && len(c.ReqBodyRaw) > 0 { + _ = buf.WriteByte('\n') + _, _ = buf.Write(c.ReqBodyRaw) + _ = buf.WriteByte('\n') + } + + _ = buf.WriteByte('\n') + + // TODO: print text repr of status code + _, _ = buf.WriteString(fmt.Sprintf("%d %s", c.Res.Status(), "")) + _ = buf.WriteByte('\n') + + c.Res.RangeHeaders(func(key string, value string) { + _, _ = buf.WriteString(key) + _ = buf.WriteByte(':') + _ = buf.WriteByte(' ') + _, _ = buf.WriteString(value) + _ = buf.WriteByte('\n') + }) + + if len(c.BodyRaw) > 0 { + _, _ = buf.Write(c.BodyRaw) + } + + _ = buf.WriteByte('\n') + + _ = buf.Flush() +} diff --git a/csvparser/types.go b/csvparser/types.go index f837506..a162925 100644 --- a/csvparser/types.go +++ b/csvparser/types.go @@ -9,10 +9,6 @@ import ( ) type ( - parseOpts struct { - sep byte - } - Type[T any] interface { Parse(data []byte) (T, int, error) //Compile(x T, writer io.Writer) error diff --git a/endpoint.go b/endpoint.go index 84a4437..c7fb355 100644 --- a/endpoint.go +++ b/endpoint.go @@ -10,13 +10,16 @@ type ( Header interface { SetHeader(k, v string) AddHeader(k, v string) - GetHeader(k string) (string, bool) + Header(k string) (string, bool) + RangeHeaders(func(string, string)) } Request interface { Header + Method() string SetMethod(string) + SetURL(*url.URL) // SetBodyStream sets the stream of body data belonging to a request. bodySize parameter is needed // when using fasthttp implementation. @@ -40,7 +43,7 @@ type ( SetStatus(status int) } - client interface { + Client interface { Request() (Request, error) Do(ctx context.Context, req Request) (Response, error) } diff --git a/examples/fasthttp/main.go b/examples/fasthttp/main.go index eb2656e..f75bb0d 100644 --- a/examples/fasthttp/main.go +++ b/examples/fasthttp/main.go @@ -24,7 +24,7 @@ func GetRepoInfo(user, repo string) (GithubRepoInfo, error) { call := withttp.NewCall[GithubRepoInfo](withttp.WithFasthttp()). WithURI(fmt.Sprintf("repos/%s/%s", user, repo)). WithMethod(http.MethodGet). - WithHeader("User-Agent", "withttp/0.1.0 See https://github.com/sonirico/withttp", false). + WithHeader("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false). WithHeaderFunc(func() (key, value string, override bool) { key = "X-Date" value = time.Now().String() @@ -85,7 +85,11 @@ func main() { log.Println(info) // Create an issue - res, err := CreateRepoIssue("sonirico", "withttp", "test", - "This is a test", "sonirico") + res, err := CreateRepoIssue( + "sonirico", + "withttp", + "test", + "This is a test", "sonirico", + ) log.Println(res, err) } diff --git a/examples/go.mod b/examples/go.mod new file mode 100644 index 0000000..9fb130a --- /dev/null +++ b/examples/go.mod @@ -0,0 +1,22 @@ +module withttp_examples + +go 1.19 + +require ( + github.com/rs/zerolog v1.28.0 + github.com/sonirico/withttp v0.5.1 +) + +require ( + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sonirico/stadio v0.6.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.39.0 // indirect + golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect +) + +replace github.com/sonirico/withttp v0.5.1 => ../ diff --git a/examples/go.sum b/examples/go.sum new file mode 100644 index 0000000..8e4d65e --- /dev/null +++ b/examples/go.sum @@ -0,0 +1,39 @@ +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY= +github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= +github.com/sonirico/stadio v0.6.0 h1:KriP6AvxI3KH1qxjk4w8PmWBfuL1THBakmIaxJ3dSBs= +github.com/sonirico/stadio v0.6.0/go.mod h1:4LwjfJhOZwHorB58wz5CNEe4Hbx3XwjLxmX/Imz2om4= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.39.0 h1:lW8mGeM7yydOqZKmwyMTaz/PH/A+CLgtmmcjv+OORfU= +github.com/valyala/fasthttp v1.39.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/examples/mock/main.go b/examples/mock/main.go index d15749a..8e975e2 100644 --- a/examples/mock/main.go +++ b/examples/mock/main.go @@ -38,7 +38,7 @@ func main() { WithURL("https://github.com/"). WithMethod(http.MethodGet). WithBasicAuth("pepito", "secret"). - WithHeader("User-Agent", "withttp/0.1.0 See https://github.com/sonirico/withttp", false). + WithHeader("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false). WithHeaderFunc(func() (key, value string, override bool) { key = "X-Date" value = time.Now().String() diff --git a/examples/singlecall/main.go b/examples/singlecall/main.go index b4672b2..3ff19f6 100644 --- a/examples/singlecall/main.go +++ b/examples/singlecall/main.go @@ -5,7 +5,9 @@ import ( "fmt" "log" "net/http" + "os" + "github.com/rs/zerolog" "github.com/sonirico/withttp" ) @@ -15,16 +17,20 @@ type GithubRepoInfo struct { } func GetRepoInfo(user, repo string) (GithubRepoInfo, error) { + l := zerolog.New(os.Stdout) - call := withttp.NewCall[GithubRepoInfo](withttp.WithFasthttp()). + call := withttp.NewCall[GithubRepoInfo](withttp.WithNetHttp()). WithURL(fmt.Sprintf("https://api.github.com/repos/%s/%s", user, repo)). + WithLogger(&l). WithMethod(http.MethodGet). - WithHeader("User-Agent", "withttp/0.1.0 See https://github.com/sonirico/withttp", false). + WithHeader("User-Agent", "withttp/0.5.1 See https://github.com/sonirico/withttp", false). WithParseJSON(). WithExpectedStatusCodes(http.StatusOK) err := call.Call(context.Background()) + call.Log(os.Stderr) + return call.BodyParsed, err } diff --git a/go.mod b/go.mod index b50b432..6a95d82 100644 --- a/go.mod +++ b/go.mod @@ -2,12 +2,14 @@ module github.com/sonirico/withttp go 1.19 -require github.com/pkg/errors v0.9.1 +require ( + github.com/pkg/errors v0.9.1 + github.com/sonirico/stadio v0.6.0 + github.com/valyala/fasthttp v1.39.0 +) require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/klauspost/compress v1.15.9 // indirect - github.com/sonirico/stadio v0.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.39.0 // indirect ) diff --git a/with_logger.go b/with_logger.go new file mode 100644 index 0000000..cbe263a --- /dev/null +++ b/with_logger.go @@ -0,0 +1,7 @@ +package withttp + +type ( + logger interface { + Printf(string, ...any) + } +) diff --git a/with_res.go b/with_res.go index 833b893..071f058 100644 --- a/with_res.go +++ b/with_res.go @@ -54,19 +54,13 @@ func WithParseStreamChan[T any](factory StreamFactory[T], out chan<- T) CallResO func WithExpectedStatusCodes[T any](states ...int) CallResOptionFunc[T] { return WithAssertion[T](func(res Response) error { - found := false for _, status := range states { if status == res.Status() { - found = true - break + return nil } } + return errors.Wrapf(ErrUnexpectedStatusCode, "want: %v, have: %d", states, res.Status()) - if !found { - return errors.Wrapf(ErrUnexpectedStatusCode, "want: %v, have: %d", states, res.Status()) - } - - return nil }) }