Skip to content

Commit

Permalink
refactor: add generic API request helper functions (#572)
Browse files Browse the repository at this point in the history
Those helpers function will be used to replace a lot of boilerplate
using in clients.

See the tests to have a better idea of how it can be used.

For now, no API clients are refactored to use those helpers, this will
happen incrementally in future PRs.
  • Loading branch information
jooola authored Dec 20, 2024
1 parent b743a33 commit 6524772
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 0 deletions.
86 changes: 86 additions & 0 deletions hcloud/client_generic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package hcloud

import (
"bytes"
"context"
"encoding/json"
)

func getRequest[Schema any](ctx context.Context, client *Client, url string) (*Schema, *Response, error) {
req, err := client.NewRequest(ctx, "GET", url, nil)
if err != nil {
return nil, nil, err
}

var respBody Schema
resp, err := client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}

return &respBody, resp, nil
}

func postRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (*Schema, *Response, error) {
reqBodyBytes, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}

req, err := client.NewRequest(ctx, "POST", url, bytes.NewReader(reqBodyBytes))
if err != nil {
return nil, nil, err
}

var respBody Schema
resp, err := client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}

return &respBody, resp, nil
}

func putRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (*Schema, *Response, error) {
reqBodyBytes, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}

req, err := client.NewRequest(ctx, "PUT", url, bytes.NewReader(reqBodyBytes))
if err != nil {
return nil, nil, err
}

var respBody Schema
resp, err := client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}

return &respBody, resp, nil
}

func deleteRequest[Schema any](ctx context.Context, client *Client, url string) (*Schema, *Response, error) {
req, err := client.NewRequest(ctx, "DELETE", url, nil)
if err != nil {
return nil, nil, err
}

var respBody Schema
resp, err := client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}

return &respBody, resp, nil
}

func deleteRequestNoResult(ctx context.Context, client *Client, url string) (*Response, error) {
req, err := client.NewRequest(ctx, "DELETE", url, nil)
if err != nil {
return nil, err
}

return client.Do(req, nil)
}
115 changes: 115 additions & 0 deletions hcloud/client_generic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package hcloud

import (
"io"
"net/http"
"testing"

"github.com/stretchr/testify/require"

"github.com/hetznercloud/hcloud-go/v2/hcloud/exp/mockutil"
"github.com/hetznercloud/hcloud-go/v2/hcloud/schema"
)

func TestGenericRequest(t *testing.T) {
t.Run("get", func(t *testing.T) {
ctx, server, client := makeTestUtils(t)

server.Expect([]mockutil.Request{
{
Method: "GET", Path: "/resource",
Status: 200,
JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}},
},
})

respBody, resp, err := getRequest[schema.ActionGetResponse](ctx, client, "/resource")
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, respBody)

require.Equal(t, int64(1234), respBody.Action.ID)
})

t.Run("post", func(t *testing.T) {
ctx, server, client := makeTestUtils(t)

server.Expect([]mockutil.Request{
{
Method: "POST", Path: "/resource",
Want: func(t *testing.T, r *http.Request) {
bodyBytes, err := io.ReadAll(r.Body)
require.NoError(t, err)
require.JSONEq(t, `{"hello": "world"}`, string(bodyBytes))
},
Status: 200,
JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}},
},
})

respBody, resp, err := postRequest[schema.ActionGetResponse](ctx, client, "/resource", map[string]string{"hello": "world"})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, respBody)

require.Equal(t, int64(1234), respBody.Action.ID)
})

t.Run("put", func(t *testing.T) {
ctx, server, client := makeTestUtils(t)

server.Expect([]mockutil.Request{
{
Method: "PUT", Path: "/resource",
Want: func(t *testing.T, r *http.Request) {
bodyBytes, err := io.ReadAll(r.Body)
require.NoError(t, err)
require.JSONEq(t, `{"hello": "world"}`, string(bodyBytes))
},
Status: 200,
JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}},
},
})

respBody, resp, err := putRequest[schema.ActionGetResponse](ctx, client, "/resource", map[string]string{"hello": "world"})
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, respBody)

require.Equal(t, int64(1234), respBody.Action.ID)
})

t.Run("delete", func(t *testing.T) {
ctx, server, client := makeTestUtils(t)

server.Expect([]mockutil.Request{
{
Method: "DELETE", Path: "/resource",
Status: 200,
JSON: schema.ActionGetResponse{Action: schema.Action{ID: 1234}},
},
})

respBody, resp, err := deleteRequest[schema.ActionGetResponse](ctx, client, "/resource")
require.NoError(t, err)
require.NotNil(t, resp)
require.NotNil(t, respBody)

require.Equal(t, int64(1234), respBody.Action.ID)
})

t.Run("delete no result", func(t *testing.T) {
ctx, server, client := makeTestUtils(t)

server.Expect([]mockutil.Request{
{
Method: "DELETE", Path: "/resource",
Status: 204,
},
})

resp, err := deleteRequestNoResult(ctx, client, "/resource")
require.NoError(t, err)
require.NotNil(t, resp)
})
}
15 changes: 15 additions & 0 deletions hcloud/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,24 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/hetznercloud/hcloud-go/v2/hcloud/exp/mockutil"
"github.com/hetznercloud/hcloud-go/v2/hcloud/schema"
)

func makeTestUtils(t *testing.T) (context.Context, *mockutil.Server, *Client) {
ctx := context.Background()

server := mockutil.NewServer(t, nil)

client := NewClient(
WithEndpoint(server.URL),
WithRetryOpts(RetryOpts{BackoffFunc: ConstantBackoff(0), MaxRetries: 5}),
WithPollOpts(PollOpts{BackoffFunc: ConstantBackoff(0)}),
)

return ctx, server, client
}

type testEnv struct {
Server *httptest.Server
Mux *http.ServeMux
Expand Down

0 comments on commit 6524772

Please sign in to comment.