-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
cbeaefc
commit 7b1ec39
Showing
4 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package http | ||
|
||
type Client[T any] interface { | ||
Get(uri string, opts ...*RequestOptions) (data T, err error) | ||
Post(uri string, opts ...*RequestOptions) (data T, err error) | ||
Patch(uri string, opts ...*RequestOptions) (data T, err error) | ||
Put(uri string, opts ...*RequestOptions) (data T, err error) | ||
Delete(uri string, opts ...*RequestOptions) (data T, err error) | ||
Request(method, uri string, opts ...*RequestOptions) (data T, err error) | ||
} | ||
|
||
type httpClient[T any] struct { | ||
opts *Options | ||
} | ||
|
||
func (c httpClient[T]) Get(uri string, opts ...*RequestOptions) (data T, err error) { | ||
return Get[T](c.opts.urlOf(uri), opts...) | ||
} | ||
|
||
func (c httpClient[T]) Post(uri string, opts ...*RequestOptions) (data T, err error) { | ||
return Post[T](c.opts.urlOf(uri), opts...) | ||
} | ||
|
||
func (c httpClient[T]) Patch(uri string, opts ...*RequestOptions) (data T, err error) { | ||
return Patch[T](c.opts.urlOf(uri), opts...) | ||
} | ||
|
||
func (c httpClient[T]) Put(uri string, opts ...*RequestOptions) (data T, err error) { | ||
return Put[T](c.opts.urlOf(uri), opts...) | ||
} | ||
|
||
func (c httpClient[T]) Delete(uri string, opts ...*RequestOptions) (data T, err error) { | ||
return Delete[T](c.opts.urlOf(uri), opts...) | ||
} | ||
|
||
func (c httpClient[T]) Request(method, uri string, opts ...*RequestOptions) (data T, err error) { | ||
return Request[T](method, c.opts.urlOf(uri), opts...) | ||
} | ||
|
||
// ========================== | ||
|
||
func NewClient[T any](opts ...*Options) Client[T] { | ||
return &httpClient[T]{ | ||
opts: defaultOptions(opts...), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package http | ||
|
||
import ( | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"reflect" | ||
"strings" | ||
|
||
"github.com/Cyberpull/gokit/errors" | ||
) | ||
|
||
type Response *http.Response | ||
type Callback func(resp Response) (err error) | ||
|
||
func Get[T any](url string, opts ...*RequestOptions) (data T, err error) { | ||
return Request[T](http.MethodGet, url, opts...) | ||
} | ||
|
||
func Post[T any](url string, opts ...*RequestOptions) (data T, err error) { | ||
return Request[T](http.MethodPost, url, opts...) | ||
} | ||
|
||
func Patch[T any](url string, opts ...*RequestOptions) (data T, err error) { | ||
return Request[T](http.MethodPatch, url, opts...) | ||
} | ||
|
||
func Put[T any](url string, opts ...*RequestOptions) (data T, err error) { | ||
return Request[T](http.MethodPut, url, opts...) | ||
} | ||
|
||
func Delete[T any](url string, opts ...*RequestOptions) (data T, err error) { | ||
return Request[T](http.MethodDelete, url, opts...) | ||
} | ||
|
||
func Request[T any](method, url string, opts ...*RequestOptions) (data T, err error) { | ||
opt := defaultRequestOptions(opts...) | ||
|
||
err = RequestCallback(method, url, opt, func(resp Response) (err2 error) { | ||
defer resp.Body.Close() | ||
|
||
var b []byte | ||
|
||
if b, err2 = responseData(resp); err2 != nil { | ||
return | ||
} | ||
|
||
vType := reflect.TypeOf(data) | ||
|
||
contentType := resp.Header.Get("Content-Type") | ||
|
||
if opt.ExpectsJSON || strings.HasPrefix(contentType, "application/json") { | ||
// Parse JSON Content | ||
switch vType.Kind() { | ||
case reflect.Pointer: | ||
data = reflect.New(vType).Interface().(T) | ||
err2 = json.Unmarshal(b, data) | ||
default: | ||
err2 = json.Unmarshal(b, &data) | ||
} | ||
|
||
return | ||
} | ||
|
||
// Get Content | ||
if vType.Kind() == reflect.String { | ||
data = reflect.ValueOf(string(b)).Interface().(T) | ||
return | ||
} | ||
|
||
err2 = errors.New("Invalid return type") | ||
|
||
return | ||
}) | ||
|
||
return | ||
} | ||
|
||
func RequestCallback(method, url string, opts *RequestOptions, callback Callback) (err error) { | ||
var req *http.Request | ||
var resp *http.Response | ||
|
||
method = strings.ToUpper(method) | ||
|
||
opt := defaultRequestOptions(opts) | ||
|
||
if req, err = http.NewRequest(method, url, opt.Body); err != nil { | ||
return | ||
} | ||
|
||
opt.mergeTo(req) | ||
|
||
if resp, err = http.DefaultClient.Do(req); err != nil { | ||
return | ||
} | ||
|
||
err = callback(resp) | ||
|
||
return | ||
} | ||
|
||
// Private Functions =========================== | ||
|
||
func trim(uri string) string { | ||
uri = strings.TrimSpace(uri) | ||
uri = strings.Trim(uri, "/") | ||
return uri | ||
} | ||
|
||
func join(paths ...string) string { | ||
entries := make([]string, 0) | ||
|
||
for _, path := range paths { | ||
path = trim(path) | ||
|
||
if path != "" { | ||
entries = append(entries, path) | ||
} | ||
} | ||
|
||
return strings.Join(entries, "/") | ||
} | ||
|
||
func isUrl(uri string) bool { | ||
return (strings.HasPrefix(uri, "https://") || | ||
strings.HasPrefix(uri, "http://")) | ||
} | ||
|
||
func isOk(statusCode int) bool { | ||
return statusCode >= http.StatusOK && statusCode < 300 | ||
} | ||
|
||
func responseData(resp *http.Response) (data []byte, err error) { | ||
data, err = io.ReadAll(resp.Body) | ||
|
||
if err != nil || isOk(resp.StatusCode) { | ||
return | ||
} | ||
|
||
if len(data) == 0 { | ||
data = []byte(resp.Status) | ||
} | ||
|
||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package http | ||
|
||
type Options struct { | ||
BaseURL string | ||
} | ||
|
||
func (o *Options) urlOf(uri string) string { | ||
if o.BaseURL == "" || isUrl(uri) { | ||
return uri | ||
} | ||
|
||
return join(o.BaseURL, uri) | ||
} | ||
|
||
// =================== | ||
|
||
func defaultOptions(opts ...*Options) *Options { | ||
if len(opts) > 0 { | ||
return opts[0] | ||
} | ||
|
||
return &Options{} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package http | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
) | ||
|
||
type RequestOptions struct { | ||
ExpectsJSON bool | ||
Header http.Header | ||
Body io.Reader | ||
} | ||
|
||
func (o *RequestOptions) mergeTo(req *http.Request) { | ||
if req == nil { | ||
return | ||
} | ||
|
||
// Merge Headers | ||
if o.Header != nil { | ||
for k, v := range o.Header { | ||
req.Header[k] = v | ||
} | ||
} | ||
|
||
// Merge Headers | ||
// Merge Headers | ||
// Merge Headers | ||
// Merge Headers | ||
// Merge Headers | ||
// Merge Headers | ||
} | ||
|
||
// =================== | ||
|
||
func defaultRequestOptions(opts ...*RequestOptions) *RequestOptions { | ||
if len(opts) > 0 && opts[0] != nil { | ||
return opts[0] | ||
} | ||
|
||
return &RequestOptions{} | ||
} |