Skip to content

Commit

Permalink
Merge pull request #24 from goinsane/develop
Browse files Browse the repository at this point in the history
v0.11.0
  • Loading branch information
orkunkaraduman authored Dec 25, 2023
2 parents afad3be + 6eb959a commit c85dfbe
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 114 deletions.
40 changes: 24 additions & 16 deletions caller.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,11 @@ func (c *Caller) Call(ctx context.Context, in interface{}, opts ...CallOption) (

var data []byte
if inVal := reflect.ValueOf(in); !options.ForceBody &&
(in == nil || inVal.Kind() == reflect.Struct || (inVal.Kind() == reflect.Ptr && inVal.Elem().Kind() == reflect.Struct)) &&
(c.method == http.MethodHead || c.method == http.MethodGet || c.method == http.MethodDelete) {
if !(in == nil ||
inVal.Kind() == reflect.Struct || (inVal.Kind() == reflect.Ptr && inVal.Elem().Kind() == reflect.Struct)) {
return nil, errors.New("input must be nil or struct or struct pointer")
}
var values url.Values
values, err = structToValues(in)
if err != nil {
Expand All @@ -54,7 +57,7 @@ func (c *Caller) Call(ctx context.Context, in interface{}, opts ...CallOption) (
} else {
data, err = json.Marshal(in)
if err != nil {
return nil, fmt.Errorf("unable to marshal input: %w", err)
return nil, fmt.Errorf("unable to encode input: %w", err)
}
data = append(data, '\n')
req.Header.Set("Content-Type", "application/json; charset=utf-8")
Expand All @@ -78,11 +81,6 @@ func (c *Caller) Call(ctx context.Context, in interface{}, opts ...CallOption) (
if options.MaxResponseBodySize > 0 {
rd = io.LimitReader(resp.Body, options.MaxResponseBodySize)
}
data, err = io.ReadAll(rd)
if err != nil {
return result, fmt.Errorf("unable to read response body: %w", err)
}
result.Data = data

if contentType := resp.Header.Get("Content-Type"); contentType != "" {
validMediaTypes := []string{"application/json"}
Expand All @@ -95,11 +93,11 @@ func (c *Caller) Call(ctx context.Context, in interface{}, opts ...CallOption) (
return result, &InvalidContentTypeError{err, contentType}
}
if mediaType == "text/plain" {
text := bytes.TrimSpace(data)
if len(text) > 1024 {
text = text[:1024]
data, err = io.ReadAll(io.LimitReader(rd, 1024))
if err != nil {
return result, fmt.Errorf("unable to read response body: %w", err)
}
return result, &PlainTextError{errors.New(string(text))}
return result, &PlainTextError{errors.New(string(data))}
}
}

Expand All @@ -114,11 +112,10 @@ func (c *Caller) Call(ctx context.Context, in interface{}, opts ...CallOption) (
return result, fmt.Errorf("unable to copy output: %w", err)
}

//if len(data) > 0 || (isErr && req.Method != http.MethodHead) {
if len(data) > 0 {
err = json.Unmarshal(data, copiedOutVal.Interface())
if req.Method != http.MethodHead {
err = json.NewDecoder(rd).Decode(copiedOutVal.Interface())
if err != nil {
return result, fmt.Errorf("unable to unmarshal response body: %w", err)
return result, fmt.Errorf("unable to decode response body: %w", err)
}
}

Expand Down Expand Up @@ -163,6 +160,15 @@ func NewFactory(client *http.Client, u *url.URL, opts ...CallOption) (f *Factory

// Caller creates a new Caller with the given endpoint and method.
func (f *Factory) Caller(endpoint string, method string, out interface{}, opts ...CallOption) *Caller {
method = strings.ToUpper(method)

switch method {
case http.MethodHead, http.MethodGet, http.MethodDelete:
case http.MethodPost, http.MethodPut, http.MethodPatch:
default:
panic(fmt.Errorf("method %q not allowed", method))
}

result := &Caller{
options: f.options.Clone(),
client: f.client,
Expand All @@ -172,15 +178,17 @@ func (f *Factory) Caller(endpoint string, method string, out interface{}, opts .
Path: f.url.Path,
RawQuery: "",
},
method: strings.ToUpper(method),
method: method,
out: out,
}

if !strings.HasPrefix(result.url.Path, "/") {
result.url.Path = "/" + result.url.Path
}
if endpoint != "" {
result.url.Path = path.Join(result.url.Path, endpoint)
}

newJoinCallOption(opts...).apply(result.options)
return result
}
8 changes: 4 additions & 4 deletions calloption.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ func (o *callOptions) Clone() *callOptions {
}

// WithRequestHeader returns a CallOption that sets the given http headers to the request headers.
func WithRequestHeader(header ...http.Header) CallOption {
func WithRequestHeader(headers ...http.Header) CallOption {
return newFuncCallOption(func(options *callOptions) {
for _, hdr := range header {
for _, hdr := range headers {
for k, v := range hdr.Clone() {
k = textproto.CanonicalMIMEHeaderKey(k)
options.RequestHeader[k] = v
Expand All @@ -79,9 +79,9 @@ func WithRequestHeader(header ...http.Header) CallOption {
}

// WithAdditionalRequestHeader returns a CallOption that adds the given http headers to the request headers.
func WithAdditionalRequestHeader(header ...http.Header) CallOption {
func WithAdditionalRequestHeader(headers ...http.Header) CallOption {
return newFuncCallOption(func(options *callOptions) {
for _, hdr := range header {
for _, hdr := range headers {
for k, v := range hdr {
for _, v2 := range v {
options.RequestHeader.Add(k, v2)
Expand Down
8 changes: 3 additions & 5 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@ import "net/http"
// It is used in DoFunc and MiddlewareFunc.
type Request struct {
*http.Request
Data []byte
In interface{}
In interface{}
}

// Response encapsulates http.Response and gives data and output from response.
// It is returned from Caller.Call.
type Response struct {
*http.Response
Data []byte
Out interface{}
Out interface{}
}

// DoFunc is a function type to process requests from Handler.
Expand All @@ -25,4 +23,4 @@ type DoFunc func(req *Request, send SendFunc)
type MiddlewareFunc func(req *Request, send SendFunc, next DoFunc)

// SendFunc is a function type to send response in DoFunc or MiddlewareFunc.
type SendFunc func(out interface{}, code int, header ...http.Header)
type SendFunc func(out interface{}, code int, headers ...http.Header)
28 changes: 14 additions & 14 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,6 @@ package rapi

import "fmt"

// RequestError is the request error from http.Client.
// It is returned from Caller.Call.
type RequestError struct{ error error }

// Error is the implementation of error.
func (e *RequestError) Error() string {
return fmt.Errorf("request error: %w", e.error).Error()
}

// Unwrap unwraps the underlying error.
func (e *RequestError) Unwrap() error {
return e.error
}

// InvalidContentTypeError occurs when the request or response body content type is invalid.
type InvalidContentTypeError struct {
error error
Expand All @@ -37,6 +23,20 @@ func (e *InvalidContentTypeError) ContentType() string {
return e.contentType
}

// RequestError is the request error from http.Client.
// It is returned from Caller.Call.
type RequestError struct{ error error }

// Error is the implementation of error.
func (e *RequestError) Error() string {
return fmt.Errorf("request error: %w", e.error).Error()
}

// Unwrap unwraps the underlying error.
func (e *RequestError) Unwrap() error {
return e.error
}

// PlainTextError is the plain text error returned from http server.
// It is returned from Caller.Call.
type PlainTextError struct{ error error }
Expand Down
21 changes: 3 additions & 18 deletions examples/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,11 @@ func main() {
rapi.WithReadTimeout(60*time.Second),
rapi.WithWriteTimeout(60*time.Second),
rapi.WithAllowEncoding(true),
rapi.WithNotFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
})),
)

handler.Handle("/").
Register("", nil, handleUnimplemented)

handler.Handle("/echo", rapi.WithMiddleware(authMiddleware)).
Register("", nil, handleEcho)

handler.Handle("/ping").
Register(http.MethodGet, new(messages.PingRequest), handlePing)

Expand Down Expand Up @@ -60,18 +57,6 @@ func authMiddleware(req *rapi.Request, send rapi.SendFunc, do rapi.DoFunc) {
do(req, send)
}

func handleUnimplemented(req *rapi.Request, send rapi.SendFunc) {
send(&messages.ErrorReply{
ErrorMsg: http.StatusText(http.StatusNotImplemented),
}, http.StatusNotImplemented)
}

func handleEcho(req *rapi.Request, send rapi.SendFunc) {
hdr := http.Header{}
hdr.Set("X-Request-Method", req.Method)
send(req.In, http.StatusOK, hdr)
}

func handlePing(req *rapi.Request, send rapi.SendFunc) {
in := req.In.(*messages.PingRequest)
out := &messages.PingReply{
Expand Down
Loading

0 comments on commit c85dfbe

Please sign in to comment.