diff --git a/pkg/internal/httpclient/client.go b/pkg/internal/httpclient/client.go new file mode 100644 index 00000000..18c8c864 --- /dev/null +++ b/pkg/internal/httpclient/client.go @@ -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 +} diff --git a/pkg/internal/httpclient/gateway.go b/pkg/internal/httpclient/gateway.go index 6d8b8815..284a472b 100644 --- a/pkg/internal/httpclient/gateway.go +++ b/pkg/internal/httpclient/gateway.go @@ -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) @@ -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 } diff --git a/pkg/paymentmethod/client.go b/pkg/paymentmethod/client.go index 7b3851b2..5cc52d52 100644 --- a/pkg/paymentmethod/client.go +++ b/pkg/paymentmethod/client.go @@ -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" @@ -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 } diff --git a/pkg/paymentmethod/payment_method_test.go b/pkg/paymentmethod/client_test.go similarity index 98% rename from pkg/paymentmethod/payment_method_test.go rename to pkg/paymentmethod/client_test.go index 264b24a2..1729ffca 100644 --- a/pkg/paymentmethod/payment_method_test.go +++ b/pkg/paymentmethod/client_test.go @@ -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) } diff --git a/test/integration/payment_method_test.go b/test/integration/payment_method_test.go index 47e0232b..92aee1a9 100644 --- a/test/integration/payment_method_test.go +++ b/test/integration/payment_method_test.go @@ -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")