Skip to content

Commit

Permalink
Merge pull request #6 from mercadopago/feature/implements-client-generic
Browse files Browse the repository at this point in the history
Feature/Implements client generic
  • Loading branch information
meliguilhermefernandes authored Feb 7, 2024
2 parents 415b87c + a04fea5 commit 0719d5e
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 79 deletions.
137 changes: 137 additions & 0 deletions pkg/internal/httpclient/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package httpclient

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"runtime"
"strings"

"github.com/google/uuid"
"github.com/mercadopago/sdk-go/pkg/config"
)

const (
currentSDKVersion string = "x.x.x"
productID string = "abc"
accept string = "application/json"
contentType string = "application/json; charset=UTF-8"

headerProductID = "X-Product-Id"
headerAccept = "Accept"
headerContentType = "Content-Type"
headerUserAgent = "User-Agent"
headerTrackingID = "X-Tracking-Id"
headerRequestID = "X-Request-Id"
headerAuthorization = "Authorization"
headerIdempotency = "X-Idempotency-Key"

headerCorporationID = "X-Corporation-Id"
headerIntegratorID = "X-Integrator-Id"
headerPlatformID = "X-Platform-Id"
)

var (
userAgent = fmt.Sprintf("MercadoPago Go SDK/%s", currentSDKVersion)
trackingID = fmt.Sprintf("platform:%s,type:SDK%s,so;", runtime.Version(), currentSDKVersion)
)

// Get makes requests with the GET method
// Will return the struct specified in Generics
func Get[T any](ctx context.Context, cfg *config.Config, path string) (*T, error) {
req, err := makeRequest(ctx, cfg, http.MethodGet, path, nil)
if err != nil {
return nil, err
}

return send[T](cfg.Requester, req)
}

// Post makes requests with the POST method
// Will return the struct specified in Generics
func Post[T any](ctx context.Context, cfg *config.Config, path string, body any) (*T, error) {
req, err := makeRequest(ctx, cfg, http.MethodPost, path, body)
if err != nil {
return nil, err
}

return send[T](cfg.Requester, req)
}

// Put makes requests with the PUT method
// Will return the struct specified in Generics
func Put[T any](ctx context.Context, cfg *config.Config, path string, body any) (*T, error) {
req, err := makeRequest(ctx, cfg, http.MethodPut, path, body)
if err != nil {
return nil, err
}

return send[T](cfg.Requester, req)
}

// Delete makes requests with the DELETE method
// Will return the struct specified in Generics
func Delete[T any](ctx context.Context, cfg *config.Config, path string, body any) (*T, error) {
req, err := makeRequest(ctx, cfg, http.MethodDelete, path, body)
if err != nil {
return nil, err
}

return send[T](cfg.Requester, req)
}

func makeRequest(ctx context.Context, cfg *config.Config, method, path string, body any) (*http.Request, error) {
req, err := buildHTTPRequest(ctx, method, path, body)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}

makeHeaders(req, cfg)

return req, nil
}

func makeHeaders(req *http.Request, cfg *config.Config) {
req.Header.Set(headerProductID, productID)
req.Header.Set(headerAccept, accept)
req.Header.Set(headerContentType, contentType)
req.Header.Set(headerUserAgent, userAgent)
req.Header.Set(headerTrackingID, trackingID)
req.Header.Set(headerAuthorization, "Bearer "+cfg.AccessToken)
req.Header.Set(headerIdempotency, uuid.New().String())
req.Header.Set(headerRequestID, uuid.New().String())

if cfg.CorporationID != "" {
req.Header.Set(headerCorporationID, cfg.CorporationID)
}
if cfg.IntegratorID != "" {
req.Header.Set(headerIntegratorID, cfg.IntegratorID)
}
if cfg.PlatformID != "" {
req.Header.Set(headerPlatformID, cfg.PlatformID)
}
}

func buildHTTPRequest(ctx context.Context, method, url string, body any) (*http.Request, error) {
b, err := buildBody(body)
if err != nil {
return nil, err
}

return http.NewRequestWithContext(ctx, method, url, b)
}

func buildBody(body any) (io.Reader, error) {
if body == nil {
return nil, nil
}

b, err := json.Marshal(&body)
if err != nil {
return nil, fmt.Errorf("error marshaling request body: %w", err)
}

return strings.NewReader(string(b)), nil
}
63 changes: 11 additions & 52 deletions pkg/internal/httpclient/gateway.go
Original file line number Diff line number Diff line change
@@ -1,65 +1,15 @@
package httpclient

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"runtime"

"github.com/google/uuid"
"github.com/mercadopago/sdk-go/pkg/config"
"github.com/mercadopago/sdk-go/pkg/internal/requester"
)

const (
currentSDKVersion string = "x.x.x"
productID string = "abc"
accept string = "application/json"
contentType string = "application/json; charset=UTF-8"

productIDHeader = "X-Product-Id"
acceptHeader = "Accept"
contentTypeHeader = "Content-Type"
userAgentHeader = "User-Agent"
trackingIDHeader = "X-Tracking-Id"
authorizationHeader = "Authorization"
idempotencyHeader = "X-Idempotency-Key"

corporationIDHeader = "X-Corporation-Id"
integratorIDHeader = "X-Integrator-Id"
platformIDHeader = "X-Platform-Id"
)

var (
userAgent = fmt.Sprintf("MercadoPago Go SDK/%s", currentSDKVersion)
trackingID = fmt.Sprintf("platform:%s,type:SDK%s,so;", runtime.Version(), currentSDKVersion)
)

// Send wraps needed options before send api call.
func Send(ctx context.Context, cfg *config.Config, req *http.Request) ([]byte, error) {
req.Header.Set(productIDHeader, productID)
req.Header.Set(acceptHeader, accept)
req.Header.Set(contentTypeHeader, contentType)
req.Header.Set(userAgentHeader, userAgent)
req.Header.Set(trackingIDHeader, trackingID)
req.Header.Set(authorizationHeader, "Bearer "+cfg.AccessToken)
req.Header.Set(idempotencyHeader, uuid.New().String())

if cfg.CorporationID != "" {
req.Header.Set(corporationIDHeader, cfg.CorporationID)
}
if cfg.IntegratorID != "" {
req.Header.Set(integratorIDHeader, cfg.IntegratorID)
}
if cfg.PlatformID != "" {
req.Header.Set(platformIDHeader, cfg.PlatformID)
}

return send(ctx, cfg.Requester, req)
}

func send(_ context.Context, requester requester.Requester, req *http.Request) ([]byte, error) {
func send[T any](requester requester.Requester, req *http.Request) (*T, error) {
res, err := requester.Do(req)
if err != nil {
return nil, fmt.Errorf("transport level error: %w", err)
Expand All @@ -84,5 +34,14 @@ func send(_ context.Context, requester requester.Requester, req *http.Request) (
}
}

return makeResponse[T](response)
}

func makeResponse[T any](b []byte) (*T, error) {
var response *T
if err := json.Unmarshal(b, &response); err != nil {
return nil, err
}

return response, nil
}
25 changes: 5 additions & 20 deletions pkg/paymentmethod/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package paymentmethod

import (
"context"
"encoding/json"
"fmt"
"net/http"

"github.com/mercadopago/sdk-go/pkg/config"
"github.com/mercadopago/sdk-go/pkg/internal/httpclient"
Expand All @@ -22,31 +19,19 @@ type Client interface {

// client is the implementation of Client.
type client struct {
config *config.Config
cfg *config.Config
}

// NewClient returns a new Payment Methods API Client.
func NewClient(c *config.Config) Client {
return &client{
config: c,
}
func NewClient(cfg *config.Config) Client {
return &client{cfg}
}

func (c *client) List(ctx context.Context) ([]Response, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("error creating request: %w", err)
}

res, err := httpclient.Send(ctx, c.config, req)
res, err := httpclient.Get[[]Response](ctx, c.cfg, url)
if err != nil {
return nil, err
}

var formatted []Response
if err := json.Unmarshal(res, &formatted); err != nil {
return nil, err
}

return formatted, nil
return *res, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,15 +123,13 @@ func TestList(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &client{
config: tt.fields.config,
}
c := &client{tt.fields.config}
got, err := c.List(tt.args.ctx)
gotErr := ""
if err != nil {
gotErr = err.Error()
}

if gotErr != tt.wantErr {
t.Errorf("client.List() error = %v, wantErr %v", err, tt.wantErr)
}
Expand Down
6 changes: 3 additions & 3 deletions test/integration/payment_method_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import (

func TestPaymentMethod(t *testing.T) {
t.Run("should_list_payment_methods", func(t *testing.T) {
c, err := config.New(os.Getenv("at"))
cfg, err := config.New(os.Getenv("at"))
if err != nil {
t.Fatal(err)
}

pmc := paymentmethod.NewClient(c)
res, err := pmc.List(context.Background())
client := paymentmethod.NewClient(cfg)
res, err := client.List(context.Background())

if res == nil {
t.Error("res can't be nil")
Expand Down

0 comments on commit 0719d5e

Please sign in to comment.