diff --git a/README.md b/README.md
index fef59f7..37c07dc 100644
--- a/README.md
+++ b/README.md
@@ -142,6 +142,27 @@ if err != nil {
| 15+ lines | 4+ lines |
+### POST a raw body with an explicit Content-Type
+
+`Body` sends a raw/streamed body and sets no Content-Type; declare one with
+`HeaderPairs` when the server expects a specific type:
+
+```go
+// application/xml
+r, err := requests.Post("http://example.com",
+ requests.Body(strings.NewReader(xmlStr)),
+ requests.HeaderPairs("Content-Type", "application/xml"))
+
+// application/octet-stream
+r, err = requests.Post("http://example.com",
+ requests.Data([]byte(binaryData)),
+ requests.HeaderPairs("Content-Type", "application/octet-stream"))
+```
+
+`Data` also deduces a Content-Type from the data type when none is set, but
+the deduction is a heuristic (e.g. XML is detected as `text/xml`, not
+`application/xml`), so set it explicitly when the exact type matters.
+
### GET a JSON object with context
diff --git a/client.go b/client.go
index e36046b..49043b2 100644
--- a/client.go
+++ b/client.go
@@ -19,14 +19,14 @@ func WithTimeout(timeout time.Duration) ClientOption {
}
}
-// WithTransport specifies a transport for client.
+// WithTransport specifies the transport for the client.
func WithTransport(transport http.RoundTripper) ClientOption {
return func(c *Client) {
c.client.Transport = transport
}
}
-// WithInterceptor specifies an interceptor for client.
+// WithInterceptor specifies the interceptor for the client.
// You can use [ChainInterceptors] to chain multiple interceptors into one.
func WithInterceptor(interceptor InterceptorFunc) ClientOption {
return func(c *Client) {
@@ -40,7 +40,7 @@ type Client struct {
interceptor InterceptorFunc
}
-// NewClient creates a new client to serve HTTP requests.
+// NewClient creates a new Client for sending HTTP requests.
func NewClient(setters ...ClientOption) *Client {
client := newDefaultClient()
for _, setter := range setters {
@@ -49,10 +49,10 @@ func NewClient(setters ...ClientOption) *Client {
return client
}
-// request is the common func to send an HTTP request.
+// request sends an HTTP request via the client.
func (c *Client) request(method, url string, opts *Options, body []byte) (*Response, error) {
ctx := opts.ctx
- if opts.Timeout > 0 { // ctx with timeout if specified
+ if opts.Timeout > 0 { // apply per-request timeout
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
defer cancel()
@@ -85,8 +85,7 @@ func (c *Client) request(method, url string, opts *Options, body []byte) (*Respo
// do sends an HTTP request and returns an HTTP response, following policy
// (such as redirects, cookies, auth) as configured on the client.
func (c *Client) do(_ context.Context, r *Request) (*Response, error) {
- // If the returned error is nil, the Response will contain
- // a non-nil Body which the user is expected to close.
+ // The response body is read and closed by newResponse.
resp, err := c.client.Do(r.Request)
if err != nil {
return nil, err
@@ -102,7 +101,7 @@ func (c *Client) do(_ context.Context, r *Request) (*Response, error) {
return newResponse(resp, r.opts)
}
-// Get sends an HTTP request with GET method.
+// Get sends an HTTP GET request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
@@ -115,7 +114,7 @@ func (c *Client) Post(url string, options ...Option) (*Response, error) {
return c.callMethod(http.MethodPost, url, options...)
}
-// Put sends an HTTP request with PUT method.
+// Put sends an HTTP PUT request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
@@ -123,7 +122,7 @@ func (c *Client) Put(url string, options ...Option) (*Response, error) {
return c.callMethod(http.MethodPut, url, options...)
}
-// Patch sends an HTTP request with PATCH method.
+// Patch sends an HTTP PATCH request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
@@ -131,7 +130,7 @@ func (c *Client) Patch(url string, options ...Option) (*Response, error) {
return c.callMethod(http.MethodPatch, url, options...)
}
-// Delete sends an HTTP request with DELETE method.
+// Delete sends an HTTP DELETE request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
diff --git a/default.go b/default.go
index 9902a93..e738b28 100644
--- a/default.go
+++ b/default.go
@@ -29,7 +29,7 @@ func getDefaultClient() *Client {
return defaultClient
}
-// InitDefaultClient initializes the default client with given options.
+// InitDefaultClient initializes the default client with the given options.
func InitDefaultClient(setters ...ClientOption) {
client := getDefaultClient()
for _, setter := range setters {
diff --git a/interceptor.go b/interceptor.go
index 7558623..151102e 100644
--- a/interceptor.go
+++ b/interceptor.go
@@ -4,13 +4,11 @@ import (
"context"
)
-// Do is called by Interceptor to complete HTTP requests.
+// Do is called by an interceptor to complete the HTTP request.
type Do func(ctx context.Context, r *Request) (*Response, error)
-// InterceptorFunc provides a hook to intercept the execution of an HTTP request
-// invocation. When an interceptor(s) is set, requests delegates all HTTP
-// client invocations to the interceptor, and it is the responsibility of the
-// interceptor to call do to complete the processing of the HTTP request.
+// InterceptorFunc intercepts an HTTP request. When set, the client delegates
+// the request to the interceptor, which must call do to complete it.
type InterceptorFunc func(ctx context.Context, r *Request, do Do) (*Response, error)
// ChainInterceptors chains multiple interceptors into one.
diff --git a/method.go b/method.go
index 8e6cc48..e342922 100644
--- a/method.go
+++ b/method.go
@@ -1,6 +1,6 @@
package requests
-// Get sends an HTTP request with GET method.
+// Get sends an HTTP GET request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
@@ -13,7 +13,7 @@ func Post(url string, options ...Option) (*Response, error) {
return getDefaultClient().Post(url, options...)
}
-// Put sends an HTTP request with PUT method.
+// Put sends an HTTP PUT request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
@@ -21,7 +21,7 @@ func Put(url string, options ...Option) (*Response, error) {
return getDefaultClient().Put(url, options...)
}
-// Patch sends an HTTP request with PATCH method.
+// Patch sends an HTTP PATCH request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
@@ -29,7 +29,7 @@ func Patch(url string, options ...Option) (*Response, error) {
return getDefaultClient().Patch(url, options...)
}
-// Delete sends an HTTP request with DELETE method.
+// Delete sends an HTTP DELETE request.
//
// On error, any Response can be ignored. A non-nil Response with a
// non-nil error only occurs when Response.StatusCode() is not 2xx.
diff --git a/options.go b/options.go
index 75ba611..744a6f0 100644
--- a/options.go
+++ b/options.go
@@ -12,7 +12,7 @@ import (
"github.com/Wenchy/requests/internal/auth"
)
-// Options defines all optional parameters for HTTP request.
+// Options defines the optional parameters for an HTTP request.
type Options struct {
ctx context.Context
@@ -48,7 +48,7 @@ type Options struct {
// Option is the functional option type.
type Option func(*Options)
-// newDefaultOptions creates a new default HTTP options.
+// newDefaultOptions creates default HTTP options.
func newDefaultOptions() *Options {
return &Options{
ctx: context.Background(),
@@ -67,7 +67,7 @@ func parseOptions(options ...Option) *Options {
// Context sets the HTTP request context.
//
-// For outgoing client request, the context controls the entire lifetime of
+// For an outgoing client request, the context controls the entire lifetime of
// a request and its response: obtaining a connection, sending the request,
// and reading the response headers and body.
func Context(ctx context.Context) Option {
@@ -111,7 +111,7 @@ func Headers[T map[string]string | http.Header](headers T) Option {
}
}
-// HeaderPairs sets HTTP headers formed by the mapping of key-value pairs.
+// HeaderPairs sets HTTP headers from key-value pairs.
// The keys should be in canonical form, as returned by
// [http.CanonicalHeaderKey]. It panics if len(kv) is odd.
//
@@ -140,8 +140,7 @@ func HeaderPairs(kv ...string) Option {
return Headers(headers)
}
-// Params sets the given query parameters into the URL query string.
-// Two types are supported:
+// Params sets the URL query string parameters. Two types are supported:
//
// # Type 1: map[string]string
//
@@ -176,7 +175,7 @@ func Params[T map[string]string | url.Values](params T) Option {
}
}
-// ParamPairs sets the query parameters formed by the mapping of key-value pairs.
+// ParamPairs sets query parameters from key-value pairs.
// It panics if len(kv) is odd.
//
// Values with the same key will be merged into a list:
@@ -202,7 +201,10 @@ func ParamPairs(kv ...string) Option {
return Params(params)
}
-// Body sets io.Reader to hold request body.
+// Body sets the request body from an [io.Reader]. It does not set
+// Content-Type and streams the reader as-is; declare a Content-Type via
+// [Headers] or [HeaderPairs] when the server expects one (e.g.
+// "application/xml", "text/html", "application/octet-stream").
func Body(body io.Reader) Option {
return func(opts *Options) {
opts.Body = body
@@ -210,12 +212,23 @@ func Body(body io.Reader) Option {
}
}
-// Data sets data of request body. It also deduces Content-Type based on
-// input data types:
+// Data sets the request body from data. Content-Type is resolved in two
+// cases: if the caller sets it via [Headers] or [HeaderPairs], that value
+// is used as-is; otherwise it is deduced from data's type:
//
-// 1. auto deduce by [http.DetectContentType]: io.Reader, []byte
-// 2. "application/json": struct, slice(except []byte), and map
-// 3. "text/plain": others
+// - io.Reader (e.g. *bytes.Buffer): read and detected via [http.DetectContentType]
+// - []byte or *[]byte: detected via [http.DetectContentType]
+// - struct, map, slice (except []byte): "application/json"
+// - otherwise: "text/plain", formatted with %v
+//
+// The deduction is a heuristic and may be imprecise — for example XML is
+// detected as "text/xml" (or "text/plain" without an XML declaration), not
+// "application/xml" — so set Content-Type explicitly when the exact type
+// matters.
+//
+// Non-reader pointers are dereferenced before the kind is determined, so
+// *struct and *[]byte behave like their pointed-to values. A typed nil
+// pointer such as (*T)(nil) falls into the last case and is sent as "".
func Data(data any) Option {
return func(opts *Options) {
opts.Data = data
@@ -223,9 +236,9 @@ func Data(data any) Option {
}
}
-// Form sets the given form values into the request body.
-// It also sets the Content-Type as "application/x-www-form-urlencoded".
-// Two types are supported:
+// Form sets the request body from form values, and forces Content-Type to
+// "application/x-www-form-urlencoded" (overriding any caller value). Two
+// types are supported:
//
// # Type 1: map[string]string
//
@@ -261,7 +274,7 @@ func Form[T map[string]string | url.Values](params T) Option {
}
}
-// FormPairs sets form values by the mapping of key-value pairs.
+// FormPairs sets form values from key-value pairs.
// It panics if len(kv) is odd.
//
// Values with the same key will be merged into a list:
@@ -287,8 +300,10 @@ func FormPairs(kv ...string) Option {
return Form(form)
}
-// JSON marshals the given struct as JSON into the request body.
-// It also sets the Content-Type as "application/json".
+// JSON marshals v as JSON into the request body, and forces Content-Type to
+// "application/json" (overriding any caller value). For a custom JSON-based
+// media type (e.g. "application/ld+json"), use [Data] with an explicit
+// Content-Type instead.
func JSON(v any) Option {
return func(opts *Options) {
opts.JSON = v
@@ -296,8 +311,9 @@ func JSON(v any) Option {
}
}
-// Files sets files to a map of (field, fileHandler).
-// It also sets the Content-Type as "multipart/form-data".
+// Files sets files as a map of field to file handler, and forces
+// Content-Type to "multipart/form-data" with the multipart boundary
+// (overriding any caller value).
func Files(files map[string]*os.File) Option {
return func(opts *Options) {
if opts.Files != nil {
@@ -311,21 +327,21 @@ func Files(files map[string]*os.File) Option {
}
}
-// ToText unmarshals HTTP response body to string.
+// ToText captures the response body as text into the string pointed to by v.
func ToText(v *string) Option {
return func(opts *Options) {
opts.ToText = v
}
}
-// ToJSON unmarshals HTTP response body to given struct as JSON.
+// ToJSON decodes the response body into v as JSON.
func ToJSON(v any) Option {
return func(opts *Options) {
opts.ToJSON = v
}
}
-// BasicAuth is the option to implement HTTP Basic Auth.
+// BasicAuth enables HTTP Basic Auth with the given username and password.
func BasicAuth(username, password string) Option {
return func(opts *Options) {
opts.AuthInfo = &auth.AuthInfo{
@@ -343,8 +359,8 @@ func Timeout(timeout time.Duration) Option {
}
}
-// Dump dumps outgoing client request and response to the corresponding
-// input param (req or resp) if not nil.
+// Dump dumps the outgoing request into req and the response into resp,
+// when the corresponding pointer is non-nil.
//
// Refer:
// - https://pkg.go.dev/net/http/httputil#DumpRequestOut
@@ -356,8 +372,8 @@ func Dump(req, resp *string) Option {
}
}
-// Interceptor prepends an interceptor to environment interceptors for current
-// request only.
+// Interceptor prepends an interceptor to the client interceptors for the
+// current request only.
func Interceptor(interceptor InterceptorFunc) Option {
return func(opts *Options) {
opts.Interceptor = interceptor
diff --git a/request.go b/request.go
index c08a08e..8cf4894 100644
--- a/request.go
+++ b/request.go
@@ -1,6 +1,6 @@
-// Package requests is an elegant and simple HTTP library for golang, built for human beings.
+// Package requests is an elegant and simple HTTP library for Go, built for human beings.
//
-// This package mimics the implementation of the classic Python package Requests(https://requests.readthedocs.io/)
+// It mimics the classic Python Requests library (https://requests.readthedocs.io/).
package requests
import (
@@ -16,7 +16,7 @@ import (
"github.com/Wenchy/requests/internal/auth"
)
-// Request is a wrapper of http.Request.
+// Request wraps [http.Request].
type Request struct {
*http.Request
opts *Options
@@ -28,7 +28,7 @@ func (r *Request) Bytes() []byte {
return r.body
}
-// Text parses the HTTP request body as string.
+// Text returns the HTTP request body as a string.
func (r *Request) Text() string {
return string(r.body)
}
@@ -67,7 +67,7 @@ func newRequest(ctx context.Context, method, url string, opts *Options, body []b
// request sends an HTTP request.
func request(c *Client, method, url string, opts *Options) (*Response, error) {
- // NOTE: get the body size from io.Reader. It is costy for large body.
+ // NOTE: reading the body into memory is costly for large bodies.
body := bytes.NewBuffer(nil)
if opts.Body != nil {
_, err := io.Copy(body, opts.Body)
@@ -79,27 +79,28 @@ func request(c *Client, method, url string, opts *Options) (*Response, error) {
return c.request(method, url, opts, body.Bytes())
}
-// requestData sends an HTTP request to the specified URL, with raw string
-// as the request body.
+// requestData sends an HTTP request with opts.Data as the body. It deduces
+// Content-Type only when the caller has not set one.
func requestData(c *Client, method, url string, opts *Options) (*Response, error) {
body := bytes.NewBuffer(nil)
if opts.Data != nil {
- contentType, bytes, err := deduceContentTypeAndBody(opts.Data)
+ dataBytes, err := dataToBody(opts.Data)
if err != nil {
return nil, err
}
- _, err = body.Write(bytes)
- if err != nil {
+ // Deduce Content-Type only when the caller has not set one.
+ if opts.Headers.Get("Content-Type") == "" {
+ opts.Headers.Set("Content-Type", deduceContentType(opts.Data, dataBytes))
+ }
+ if _, err = body.Write(dataBytes); err != nil {
return nil, err
}
- opts.Headers.Set("Content-Type", contentType)
}
opts.Body = body
return c.request(method, url, opts, body.Bytes())
}
-// requestForm sends an HTTP request to the specified URL, with form's keys and
-// values URL-encoded as the request body.
+// requestForm sends an HTTP request with form values URL-encoded as the body.
func requestForm(c *Client, method, url string, opts *Options) (*Response, error) {
body := bytes.NewBuffer(nil)
if opts.Form != nil {
@@ -114,7 +115,7 @@ func requestForm(c *Client, method, url string, opts *Options) (*Response, error
return c.request(method, url, opts, body.Bytes())
}
-// requestJSON sends an HTTP request, and encode request body as json.
+// requestJSON sends an HTTP request with opts.JSON encoded as JSON in the body.
func requestJSON(c *Client, method, url string, opts *Options) (*Response, error) {
body := bytes.NewBuffer(nil)
if opts.JSON != nil {
@@ -132,7 +133,7 @@ func requestJSON(c *Client, method, url string, opts *Options) (*Response, error
return c.request(method, url, opts, body.Bytes())
}
-// requestFiles sends an uploading request for multiple multipart-encoded files.
+// requestFiles sends an HTTP request with files multipart-encoded in the body.
func requestFiles(c *Client, method, url string, opts *Options) (*Response, error) {
body := bytes.NewBuffer(nil)
bodyWriter := multipart.NewWriter(body)
@@ -182,30 +183,59 @@ var (
formContentType = "application/x-www-form-urlencoded"
)
-// deduceContentTypeAndBody parses content type and request body from request data
-func deduceContentTypeAndBody(data any) (string, []byte, error) {
+// dataToBody encodes data into request body bytes. It is the shared body
+// production used whether or not Content-Type is deduced.
+func dataToBody(data any) ([]byte, error) {
if reader, ok := data.(io.Reader); ok {
- body, err := io.ReadAll(reader)
- return http.DetectContentType(body), body, err
+ return io.ReadAll(reader)
}
bodyValue := reflect.Indirect(reflect.ValueOf(data))
// A typed nil pointer (e.g. (*MyStruct)(nil)) reaches here as a non-nil
// interface but yields an invalid reflect.Value after Indirect. Guard
// against it so bodyValue.Interface() below does not panic.
if !bodyValue.IsValid() {
- return plainTextType, fmt.Appendf(nil, "%v", data), nil
+ return fmt.Appendf(nil, "%v", data), nil
}
switch bodyValue.Kind() {
case reflect.Struct, reflect.Map, reflect.Slice:
- // check slice here to differentiate between any slice vs byte slice.
// Assert against the (possibly dereferenced) bodyValue so that *[]byte
// is treated as raw bytes instead of being JSON/base64-marshaled.
if body, ok := bodyValue.Interface().([]byte); ok {
- return http.DetectContentType(body), body, nil
+ return body, nil
}
- body, err := json.Marshal(data)
- return jsonContentType, body, err
+ return json.Marshal(data)
default:
- return plainTextType, fmt.Appendf(nil, "%v", bodyValue.Interface()), nil
+ return fmt.Appendf(nil, "%v", bodyValue.Interface()), nil
+ }
+}
+
+// deduceContentType deduces the Content-Type for data from its type and the
+// already-encoded body. It is only called when the caller has not set one.
+func deduceContentType(data any, body []byte) string {
+ if _, ok := data.(io.Reader); ok {
+ return http.DetectContentType(body)
+ }
+ bodyValue := reflect.Indirect(reflect.ValueOf(data))
+ if !bodyValue.IsValid() {
+ return plainTextType
+ }
+ switch bodyValue.Kind() {
+ case reflect.Struct, reflect.Map, reflect.Slice:
+ // []byte (and *[]byte, after Indirect) is detected as raw bytes.
+ if _, ok := bodyValue.Interface().([]byte); ok {
+ return http.DetectContentType(body)
+ }
+ return jsonContentType
+ default:
+ return plainTextType
+ }
+}
+
+// deduceContentTypeAndBody deduces the Content-Type and body from data.
+func deduceContentTypeAndBody(data any) (string, []byte, error) {
+ body, err := dataToBody(data)
+ if err != nil {
+ return "", nil, err
}
+ return deduceContentType(data, body), body, nil
}
diff --git a/request_test.go b/request_test.go
index fd97d2f..e4c337c 100644
--- a/request_test.go
+++ b/request_test.go
@@ -951,3 +951,87 @@ func Test_deduceContentTypeAndBody(t *testing.T) {
})
}
}
+
+func TestContentTypeOverride(t *testing.T) {
+ var gotContentType string
+ testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ gotContentType = r.Header.Get("Content-Type")
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer testServer.Close()
+
+ tests := []struct {
+ name string
+ options []Option
+ want string
+ }{
+ {
+ name: "data deduces content type",
+ options: []Option{Data(map[string]string{"k": "v"})},
+ want: jsonContentType,
+ },
+ {
+ name: "data respects caller content type",
+ options: []Option{
+ HeaderPairs("Content-Type", "application/xml"),
+ Data(map[string]string{"k": "v"}),
+ },
+ want: "application/xml",
+ },
+ {
+ name: "json sets content type",
+ options: []Option{JSON(map[string]string{"k": "v"})},
+ want: jsonContentType,
+ },
+ {
+ name: "json forces content type (caller ignored)",
+ options: []Option{
+ HeaderPairs("Content-Type", "application/xml"),
+ JSON(map[string]string{"k": "v"}),
+ },
+ want: jsonContentType,
+ },
+ {
+ name: "form forces content type (caller ignored)",
+ options: []Option{
+ HeaderPairs("Content-Type", "application/xml"),
+ Form(map[string]string{"k": "v"}),
+ },
+ want: formContentType,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ gotContentType = ""
+ _, err := Post(testServer.URL, tt.options...)
+ assert.NoError(t, err)
+ assert.Equal(t, tt.want, gotContentType)
+ })
+ }
+}
+
+func TestDataStringVsBytesEquivalent(t *testing.T) {
+ // Data(string) and Data([]byte) with the same content must
+ // produce the same HTTP request (Content-Type and body).
+ content := `{"group":1,"version":"v1.0"}`
+ var gotContentType string
+ var gotBody []byte
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ gotContentType = r.Header.Get("Content-Type")
+ gotBody, _ = io.ReadAll(r.Body)
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer srv.Close()
+
+ capture := func(opt Option) (string, string) {
+ gotContentType, gotBody = "", nil
+ _, err := Post(srv.URL, opt)
+ assert.NoError(t, err)
+ return gotContentType, string(gotBody)
+ }
+
+ ct1, body1 := capture(Data(content))
+ ct2, body2 := capture(Data([]byte(content)))
+ assert.Equal(t, ct1, ct2, "Content-Type differs between Data(string) and Data([]byte)")
+ assert.Equal(t, body1, body2, "body differs between Data(string) and Data([]byte)")
+}
diff --git a/response.go b/response.go
index 7f95c45..d1ea48a 100644
--- a/response.go
+++ b/response.go
@@ -7,16 +7,15 @@ import (
"net/http"
)
-// Response is a wrapper of http.Response.
+// Response wraps [http.Response].
type Response struct {
*http.Response
body []byte // auto filled from Response.Body
}
-// newResponse reads and closes Response.Body. Then check the HTTP status
-// in Response.StatusCode. It will return an error with status and text
-// body embedded if status code is not 2xx, and none-nil response is also
-// returned.
+// newResponse reads and closes the response body. It returns a non-nil
+// response along with an error containing the status and text body when
+// the status code is not 2xx.
func newResponse(resp *http.Response, opts *Options) (*Response, error) {
r := &Response{
Response: resp,
@@ -24,8 +23,7 @@ func newResponse(resp *http.Response, opts *Options) (*Response, error) {
if err := r.readAndCloseBody(); err != nil {
return nil, err
}
- // return error with status and text body embedded if status code
- // is not 2xx, and response is also returned.
+ // non-2xx: return the response along with an error.
if resp.StatusCode < 200 || resp.StatusCode > 299 {
// TODO: only extracts 128 bytes from body.
return r, errors.New(resp.Status + " " + r.Text())
@@ -41,7 +39,7 @@ func newResponse(resp *http.Response, opts *Options) (*Response, error) {
return r, nil
}
-// readAndCloseBody drains all the HTTP response body stream and then closes it.
+// readAndCloseBody drains and closes the response body.
func (r *Response) readAndCloseBody() (err error) {
defer func() {
err1 := r.Response.Body.Close()
@@ -51,9 +49,7 @@ func (r *Response) readAndCloseBody() (err error) {
return err
}
-// StatusCode returns status code of HTTP response.
-//
-// NOTE: It returns -1 if response is nil.
+// StatusCode returns the HTTP response status code, or -1 if the response is nil.
func (r *Response) StatusCode() int {
if r == nil || r.Response == nil {
// return special status code -1 which is not registered with IANA.
@@ -62,16 +58,9 @@ func (r *Response) StatusCode() int {
return r.Response.StatusCode
}
-// StatusText returns a text for the HTTP status code.
-//
-// NOTE:
-// - It returns "" if response is nil.
-// - It returns the empty string if the code is unknown.
-//
-// e.g. "OK"
+// StatusText returns the HTTP status text, or "" if the response is nil.
func (r *Response) StatusText() string {
if r == nil || r.Response == nil {
- // return special status code -1 which is not registered with IANA.
return ""
}
return r.Response.Status
@@ -82,12 +71,12 @@ func (r *Response) Bytes() []byte {
return r.body
}
-// Text parses the HTTP response body as string.
+// Text returns the HTTP response body as a string.
func (r *Response) Text() string {
return string(r.body)
}
-// JSON decodes the HTTP response body as JSON format.
+// JSON decodes the HTTP response body into v as JSON.
func (r *Response) JSON(v any) error {
return json.Unmarshal(r.body, v)
}