Skip to content

Commit

Permalink
feat: add custom json/xml marsher
Browse files Browse the repository at this point in the history
  • Loading branch information
fupengl committed Dec 24, 2023
1 parent 384fecf commit 0055921
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 44 deletions.
74 changes: 50 additions & 24 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ type (
// ResponseInterceptor defines a function signature for response interceptors.
ResponseInterceptor func(resp *Response) error

RequestInterceptorChain []RequestInterceptor
// RequestInterceptorChain alias for RequestInterceptors
RequestInterceptorChain []RequestInterceptor
// ResponseInterceptorChain alias for ResponseInterceptors
ResponseInterceptorChain []ResponseInterceptor

// QuerySerializer is responsible for encoding URL query parameters.
Expand All @@ -30,7 +32,7 @@ type (
// Config holds the configuration for Surf.
Config struct {
BaseURL string
Headers http.Header
Header http.Header
Timeout time.Duration
Cookies []*http.Cookie
CookieJar *http.CookieJar
Expand All @@ -50,13 +52,18 @@ type (
MaxRedirects int

Client *http.Client

JSONMarshal func(v interface{}) ([]byte, error)
JSONUnmarshal func(data []byte, v interface{}) error
XMLMarshal func(v interface{}) ([]byte, error)
XMLUnmarshal func(data []byte, v interface{}) error
}

// RequestConfig holds the configuration for a specific HTTP request.
RequestConfig struct {
BaseURL string
Url string
Headers http.Header
Header http.Header
Method string
Cookies []*http.Cookie

Expand Down Expand Up @@ -85,6 +92,11 @@ type (
Request *http.Request

clientTrace *clientTrace

JSONMarshal func(v interface{}) ([]byte, error)
JSONUnmarshal func(data []byte, v interface{}) error
XMLMarshal func(v interface{}) ([]byte, error)
XMLUnmarshal func(data []byte, v interface{}) error
}
)

Expand Down Expand Up @@ -156,10 +168,10 @@ func (rc *RequestConfig) SetParam(key, value string) *RequestConfig {

// SetHeader sets a header in the request configuration.
func (rc *RequestConfig) SetHeader(key, value string) *RequestConfig {
if rc.Headers == nil {
rc.Headers = make(http.Header)
if rc.Header == nil {
rc.Header = make(http.Header)
}
rc.Headers.Set(key, value)
rc.Header.Set(key, value)
return rc
}

Expand Down Expand Up @@ -236,28 +248,32 @@ func (rc *RequestConfig) getRequestBody() (r io.Reader, err error) {
case string:
return bytes.NewReader([]byte(data)), err
default:
contentType := rc.Headers.Get(headerContentType)
if contentType != "" && regXmlHeader.MatchString(contentType) {
xmlData, xmlErr := xml.Marshal(data)
if xmlErr != nil {
return nil, xmlErr
contentType := rc.Header.Get(headerContentType)
if contentType != "" {
if regXmlHeader.MatchString(contentType) {
xmlData, xmlErr := rc.XMLMarshal(data)
if xmlErr != nil {
return nil, xmlErr
}
return bytes.NewReader(xmlData), nil
}
return bytes.NewReader(xmlData), nil
}
if contentType != "" && regJsonHeader.MatchString(contentType) {
jsonData, jsonErr := json.Marshal(data)
if jsonErr != nil {
return nil, jsonErr

if regJsonHeader.MatchString(contentType) {
jsonData, jsonErr := rc.JSONMarshal(data)
if jsonErr != nil {
return nil, jsonErr
}
return bytes.NewReader(jsonData), nil
}
return bytes.NewReader(jsonData), nil
}

return nil, ErrRequestDataTypeInvalid
}
}

// setContentTypeHeader sets the Content-Type header based on the request body type.
func (rc *RequestConfig) setContentTypeHeader() {
if rc.Headers.Get(headerContentType) != "" {
if rc.Header.Get(headerContentType) != "" {
return
}

Expand All @@ -284,17 +300,13 @@ func (rc *RequestConfig) mergeConfig(config *Config) *RequestConfig {
}

if rc.Client == nil {
rc.Client = config.Client
rc.Client = defaultValue(config.Client, http.DefaultClient)
}

if rc.Timeout == 0 {
rc.Timeout = config.Timeout
}

if rc.Client == nil {
rc.Client = http.DefaultClient
}

if config.CookieJar != nil {
rc.Client.Jar = *config.CookieJar
}
Expand Down Expand Up @@ -337,6 +349,20 @@ func (rc *RequestConfig) mergeConfig(config *Config) *RequestConfig {
}
}

if rc.JSONMarshal == nil {
rc.JSONMarshal = defaultValue(config.JSONMarshal, json.Marshal)
}
if rc.JSONUnmarshal == nil {
rc.JSONUnmarshal = defaultValue(config.JSONUnmarshal, json.Unmarshal)
}

if rc.XMLMarshal == nil {
rc.XMLMarshal = defaultValue(config.XMLMarshal, xml.Marshal)
}
if rc.XMLUnmarshal == nil {
rc.XMLUnmarshal = defaultValue(config.XMLUnmarshal, xml.Unmarshal)
}

// Enable http trace for Performance
rc.clientTrace = &clientTrace{}
rc.Context = rc.clientTrace.createContext(rc.Context)
Expand Down
2 changes: 1 addition & 1 deletion examples/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ type GithubApi struct {
func NewGithubApi() *GithubApi {
config := surf.Config{
BaseURL: "https://api.github.com",
Headers: map[string][]string{
Header: map[string][]string{
"Accept": {"application/vnd.github+json"},
"X-GitHub-Api-Version": {"2022-11-28"},
},
Expand Down
6 changes: 1 addition & 5 deletions multipart_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,7 @@ func (m *multipartFile) Bytes() ([]byte, error) {

if len(m.errors) > 0 {
// If there are errors, combine them into a single error and return
var errMsg string
for _, e := range m.errors {
errMsg += e.Error() + "; "
}
return nil, errors.New(errMsg[:len(errMsg)-2]) // Removing trailing "; "
return nil, errors.Join(m.errors...)
}

return m.data.Bytes(), nil
Expand Down
6 changes: 3 additions & 3 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ func WithBaseURL(url string) WithRequestConfig {
}
}

// WithHeaders sets the request headers in the request configuration.
func WithHeaders(header http.Header) WithRequestConfig {
// WithHeader sets the request header in the request configuration.
func WithHeader(header http.Header) WithRequestConfig {
return func(c *RequestConfig) {
c.Headers = header
c.Header = header
}
}

Expand Down
6 changes: 2 additions & 4 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package surf

import (
"bytes"
"encoding/json"
"encoding/xml"
"io"
"net/http"
"os"
Expand Down Expand Up @@ -33,12 +31,12 @@ func (r *Response) BodyReader() io.Reader {

// Json parses the JSON response body and stores the result in the provided variable (v).
func (r *Response) Json(v interface{}) error {
return json.Unmarshal(r.body, &v)
return r.config.JSONUnmarshal(r.body, &v)
}

// XML parses the xml response body and stores the result in the provided variable (v).
func (r *Response) XML(v interface{}) error {
return xml.Unmarshal(r.body, &v)
return r.config.XMLUnmarshal(r.body, &v)
}

// Text returns the response body as a string.
Expand Down
18 changes: 11 additions & 7 deletions surf.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (s *Surf) prepareRequest(config *RequestConfig) (*http.Request, error) {
config.Request = req

// Update global Headers Cookies
for key, values := range s.Config.Headers {
for key, values := range s.Config.Header {
for _, value := range values {
req.Header.Set(key, value)
}
Expand Down Expand Up @@ -83,7 +83,7 @@ func (s *Surf) prepareRequest(config *RequestConfig) (*http.Request, error) {
}

// Update Request Headers
for key, values := range config.Headers {
for key, values := range config.Header {
for _, value := range values {
req.Header.Add(key, value)
}
Expand Down Expand Up @@ -212,10 +212,10 @@ func (s *Surf) Request(config *RequestConfig) (*Response, error) {

// Upload performs a file upload using the provided URL, file, and optional request configuration.
func (s *Surf) Upload(url string, file *multipartFile, args ...WithRequestConfig) (resp *Response, err error) {
return s.makeRequest(url, http.MethodPost,
append(WithRequestConfigChain{
WithBody(file),
}, args...)...,
return s.makeRequest(
url,
http.MethodPost,
append(WithRequestConfigChain{WithBody(file)}, args...)...,
)
}

Expand Down Expand Up @@ -271,7 +271,7 @@ func (s *Surf) Trace(url string, args ...WithRequestConfig) (*Response, error) {
func (s *Surf) CloneDefaultConfig() *Config {
return &Config{
BaseURL: s.Config.BaseURL,
Headers: s.Config.Headers.Clone(),
Header: s.Config.Header.Clone(),
Timeout: s.Config.Timeout,
Params: cloneMap(s.Config.Params),
Query: cloneURLValues(s.Config.Query),
Expand All @@ -283,5 +283,9 @@ func (s *Surf) CloneDefaultConfig() *Config {
MaxBodyLength: s.Config.MaxBodyLength,
MaxRedirects: s.Config.MaxRedirects,
Client: s.Config.Client,
JSONMarshal: s.Config.JSONMarshal,
JSONUnmarshal: s.Config.JSONUnmarshal,
XMLMarshal: s.Config.XMLMarshal,
XMLUnmarshal: s.Config.XMLUnmarshal,
}
}
17 changes: 17 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"net/http"
"net/url"
"reflect"
"strconv"

"github.com/dsnet/compress/brotli"
Expand Down Expand Up @@ -100,3 +101,19 @@ func cloneURLValues(originalValues url.Values) url.Values {

return clonedValues
}

func isZero(value interface{}) bool {
if value == nil {
return true
}

v := reflect.ValueOf(value)
return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface())
}

func defaultValue[T any](value, defaultValue T) T {
if isZero(value) {
return defaultValue
}
return value
}
24 changes: 24 additions & 0 deletions utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package surf

import (
"testing"
)

func TestIsZero(t *testing.T) {
if bl := isZero(nil); !bl {
t.Fail()
}
if bl := isZero(make(map[string]string)); bl {
t.Fail()
}
var mapVal map[string]string
if bl := isZero(mapVal); !bl {
t.Fail()
}
if bl := isZero(""); !bl {
t.Fail()
}
if bl := isZero(0); !bl {
t.Fail()
}
}

0 comments on commit 0055921

Please sign in to comment.