diff --git a/examples/functions/json/main.go b/examples/functions/json/main.go index 9f1e2f6..88decb9 100644 --- a/examples/functions/json/main.go +++ b/examples/functions/json/main.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "net/http" + "strings" "github.com/devilcove/httpclient" ) @@ -15,12 +16,14 @@ type Answer struct { // equivalient of curl api.ipify.org?format=json func main() { var answer Answer - ip, code, err := httpclient.GetJSON(nil, answer, http.MethodGet, "http://api.ipify.org?format=json", "") + var errResp any + ip, err := httpclient.GetJSON(nil, answer, errResp, http.MethodGet, "http://api.ipify.org?format=json", "", nil) if err != nil { - log.Fatal(err) - } - if code != http.StatusOK { - log.Fatal(err, code) + if strings.Contains(err.Error(), "non ok status") { + fmt.Println(ip, err) + } else { + log.Fatal(err) + } } fmt.Println(ip) } diff --git a/examples/functions/response/main.go b/examples/functions/response/main.go index 8cd7e19..3038967 100644 --- a/examples/functions/response/main.go +++ b/examples/functions/response/main.go @@ -10,7 +10,7 @@ import ( ) func main() { - response, err := httpclient.GetResponse(nil, http.MethodGet, "http://ifconfig.me", "") + response, err := httpclient.GetResponse(nil, http.MethodGet, "http://ifconfig.me", "", nil) if err != nil { log.Fatal(err) } diff --git a/examples/method/json/main.go b/examples/method/json/main.go index 1520b5e..060da55 100644 --- a/examples/method/json/main.go +++ b/examples/method/json/main.go @@ -14,21 +14,20 @@ type Response struct { func main() { var response Response - endpoint := httpclient.JSONEndpoint[Response]{ + var errResponse any + endpoint := httpclient.JSONEndpoint[Response, any]{ URL: "https://api.ipify.org?format=json", Route: "", Method: http.MethodGet, Authorization: "", Data: nil, Response: response, + ErrorResponse: errResponse, } - answer, code, err := endpoint.GetJSON(response) + answer, err := endpoint.GetJSON(response, errResponse) if err != nil { log.Fatal(err) } - if code != http.StatusOK { - log.Fatal(err, code) - } fmt.Println(answer) } diff --git a/httpclient.go b/httpclient.go index 0caf95b..04d143e 100644 --- a/httpclient.go +++ b/httpclient.go @@ -4,12 +4,14 @@ import ( "bytes" "encoding/json" "errors" - "fmt" "net/http" "time" ) var Client http.Client +var ErrJSON = errors.New("httpclient: json error") +var ErrRequest = errors.New("httpclient: error creating http request") +var ErrStatus = errors.New("httpclient: http.Status not OK") // Endpoint struct for an http endpoint type Endpoint struct { @@ -17,16 +19,24 @@ type Endpoint struct { Method string Route string Authorization string + Headers []Header Data any } -type JSONEndpoint[T any] struct { +type JSONEndpoint[T any, R any] struct { URL string Method string Route string Authorization string + Headers []Header Data any Response T + ErrorResponse R +} + +type Header struct { + Name string + Value string } func init() { @@ -36,54 +46,65 @@ func init() { } // GetResponse returns respnse from http request to url -func GetResponse(data any, method, url, auth string) (*http.Response, error) { +func GetResponse(data any, method, url, auth string, headers []Header) (*http.Response, error) { var request *http.Request var response *http.Response var err error if data != nil { payload, err := json.Marshal(data) if err != nil { - return response, fmt.Errorf("error encoding data %w", err) + return response, ErrJSON } request, err = http.NewRequest(method, url, bytes.NewBuffer(payload)) if err != nil { - return response, fmt.Errorf("error creating http request %w", err) + return response, ErrRequest } request.Header.Set("Content-Type", "application/json") } else { request, err = http.NewRequest(method, url, nil) if err != nil { - return response, fmt.Errorf("error creating http request %w", err) + return response, ErrRequest } } if auth != "" { request.Header.Set("Authorization", auth) } + for _, header := range headers { + request.Header.Set(header.Name, header.Value) + } return Client.Do(request) } // JSON returns JSON response from http request -func GetJSON[T any](data any, resp T, method, url, auth string) (any, int, error) { - response, err := GetResponse(data, method, url, auth) +// if response.code is http.StatusOK (200) returns body decoded to resp +// if response.code is not http.Status ok returns response(*http.Response) with err set to +// 'non ok response code' +// if json decode err returns response and err set to 'json decode err' +// for any other err returns nil and err +func GetJSON[T any, R any](data any, resp T, errResponse R, method, url, auth string, headers []Header) (any, error) { + response, err := GetResponse(data, method, url, auth, headers) if err != nil { - return nil, response.StatusCode, err + return nil, err } defer response.Body.Close() if response.StatusCode != http.StatusOK { - return response, response.StatusCode, errors.New("non 200 response code") + if err := json.NewDecoder(response.Body).Decode(&errResponse); err != nil { + return response, ErrJSON + } + return errResponse, ErrStatus } if err := json.NewDecoder(response.Body).Decode(&resp); err != nil { - return nil, response.StatusCode, err + return response, ErrJSON } - return resp, response.StatusCode, nil + return resp, nil } // GetResponse returns response from http endpoint func (e *Endpoint) GetResponse() (*http.Response, error) { - return GetResponse(e.Data, e.Method, e.URL+e.Route, e.Authorization) + return GetResponse(e.Data, e.Method, e.URL+e.Route, e.Authorization, e.Headers) } // GetJSON returns JSON received from http endpoint -func (e *JSONEndpoint[T]) GetJSON(response T) (any, int, error) { - return GetJSON(e.Data, response, e.Method, e.URL+e.Route, e.Authorization) +func (e *JSONEndpoint[T, R]) GetJSON(response T, errResponse R) (any, error) { + return GetJSON(e.Data, response, errResponse, e.Method, e.URL+e.Route, e.Authorization, e.Headers) } diff --git a/httpclient_test.go b/httpclient_test.go index 6012930..a931f2b 100644 --- a/httpclient_test.go +++ b/httpclient_test.go @@ -13,7 +13,7 @@ import ( func TestGetResponse(t *testing.T) { t.Run("ip endpoint", func(t *testing.T) { - response, err := GetResponse(nil, http.MethodGet, "https://firefly.nusak.ca/ip", "") + response, err := GetResponse(nil, http.MethodGet, "https://firefly.nusak.ca/ip", "", nil) is := is.New(t) is.NoErr(err) is.Equal(response.StatusCode, http.StatusOK) @@ -26,7 +26,7 @@ func TestGetResponse(t *testing.T) { } }) t.Run("invalid endpoint", func(t *testing.T) { - response, err := GetResponse(nil, http.MethodGet, "https://firefly.nusak.ca/invalidendpoint", "") + response, err := GetResponse(nil, http.MethodGet, "https://firefly.nusak.ca/invalidendpoint", "", nil) is := is.New(t) is.NoErr(err) is.Equal(response.StatusCode, http.StatusNotFound) @@ -42,12 +42,12 @@ func TestGetResponse(t *testing.T) { jwt := struct { JWT string }{} - response, err := GetResponse(data, http.MethodPost, "https://firefly.nusak.ca/login", "") + response, err := GetResponse(data, http.MethodPost, "https://firefly.nusak.ca/login", "", nil) is := is.New(t) is.NoErr(err) defer response.Body.Close() is.NoErr(json.NewDecoder(response.Body).Decode(&jwt)) - response, err = GetResponse("", http.MethodGet, "https://firefly.nusak.ca/api/hello", jwt.JWT) + response, err = GetResponse("", http.MethodGet, "https://firefly.nusak.ca/api/hello", jwt.JWT, nil) is.NoErr(err) is.Equal(response.StatusCode, http.StatusOK) defer response.Body.Close() @@ -70,7 +70,7 @@ func TestGetResponse(t *testing.T) { }{} var data Data data.Pass = "badpass" - response, err := GetResponse(data, http.MethodPost, "https://firefly.nusak.ca/login", "") + response, err := GetResponse(data, http.MethodPost, "https://firefly.nusak.ca/login", "", nil) is := is.New(t) is.NoErr(err) defer response.Body.Close() @@ -96,16 +96,17 @@ func TestGetJSON(t *testing.T) { response := struct { JWT string }{} - e := JSONEndpoint[struct{ JWT string }]{ - URL: "http://firefly.nusak.ca", - Route: "/login", - Method: http.MethodPost, - Data: data, - Response: response, + var errResponse any + e := JSONEndpoint[struct{ JWT string }, any]{ + URL: "http://firefly.nusak.ca", + Route: "/login", + Method: http.MethodPost, + Data: data, + Response: response, + ErrorResponse: errResponse, } - answer, code, err := e.GetJSON(response) + answer, err := e.GetJSON(response, errResponse) is.NoErr(err) - is.Equal(code, http.StatusOK) answerType := fmt.Sprintf("%T", answer) is.True(answerType == "struct { JWT string }") })