Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Implements client generic #6

Merged
merged 5 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions pkg/internal/httpclient/client.go
eltinMeli marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package httpclient

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

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

const (
domainMP = "https://api.mercadopago.com"

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"
requestIDHeader = "X-Request-Id"
authorizationHeader = "Authorization"
idempotencyHeader = "X-Idempotency-Key"

corporationIDHeader = "X-Corporation-Id"
integratorIDHeader = "X-Integrator-Id"
platformIDHeader = "X-Platform-Id"
eltinMeli marked this conversation as resolved.
Show resolved Hide resolved
)

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(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())
req.Header.Set(requestIDHeader, 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)
}
}

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

var url = domainMP + path
eltinMeli marked this conversation as resolved.
Show resolved Hide resolved
eltinMeli marked this conversation as resolved.
Show resolved Hide resolved

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
}
27 changes: 6 additions & 21 deletions pkg/paymentmethod/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ package paymentmethod

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

"github.com/mercadopago/sdk-go/pkg/config"
"github.com/mercadopago/sdk-go/pkg/internal/httpclient"
)

const url = "https://api.mercadopago.com/v1/payment_methods"
const path = "/v1/payment_methods"

// Client contains the methods to interact with the Payment Methods API.
type Client interface {
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, path)
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
eltinMeli marked this conversation as resolved.
Show resolved Hide resolved
}
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