Skip to content

Commit

Permalink
Add http package
Browse files Browse the repository at this point in the history
  • Loading branch information
christianezeani committed Oct 29, 2024
1 parent cbeaefc commit 7b1ec39
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 0 deletions.
46 changes: 46 additions & 0 deletions http/client.go
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...),
}
}
145 changes: 145 additions & 0 deletions http/fn.go
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
}
23 changes: 23 additions & 0 deletions http/options.go
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{}
}
42 changes: 42 additions & 0 deletions http/request-options.go
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{}
}

0 comments on commit 7b1ec39

Please sign in to comment.