Skip to content
Snippets Groups Projects
Commit d7212af9 authored by Matt Layher's avatar Matt Layher Committed by GitHub
Browse files

Merge pull request #12 from chrigl/v2-prepare-POST

Prepared client for POST/PUT/PATCH/DELETE
parents 93500c8d 32107803
No related branches found
No related tags found
No related merge requests found
......@@ -15,12 +15,15 @@
package netbox
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"path"
"strings"
)
// A Client is a NetBox client. It can be used to retrieve network and
......@@ -46,6 +49,13 @@ func NewClient(addr string, client *http.Client) (*Client, error) {
client = &http.Client{}
}
// Append trailing slash there is none. This is necessary
// to be able to concat url parts in a correct manner.
// See NewRequest
if !strings.HasSuffix(addr, "/") {
addr = addr + "/"
}
u, err := url.Parse(addr)
if err != nil {
return nil, err
......@@ -68,25 +78,43 @@ func NewClient(addr string, client *http.Client) (*Client, error) {
// If a nil Valuer is specified, no query parameters will be sent with the
// request.
func (c *Client) NewRequest(method string, endpoint string, options Valuer) (*http.Request, error) {
rel, err := url.Parse(endpoint)
if err != nil {
return nil, err
}
return c.NewDataRequest(method, endpoint, options, nil)
}
// NewDataRequest creates a HTTP request using the input HTTP method, URL
// endpoint, a Valuer which creates URL parameters for the request, and
// a io.Reader as the body of the request.
//
// If a nil Valuer is specified, no query parameters will be sent with the
// request.
//
// If a nil io.Reader is specified, no body will be sent with the request.
func (c *Client) NewDataRequest(method string, endpoint string, options Valuer, body io.Reader) (*http.Request, error) {
// Allow specifying a base path for API requests, so if a NetBox server
// resides at a path like http://example.com/netbox/, API requests will
// be sent to http://example.com/netbox/api/...
//
// Enables support of: https://github.com/digitalocean/netbox/issues/212.
if c.u.Path != "" {
rel.Path = path.Join(c.u.Path, rel.Path)
//
// Remove leading slash if there is one. This is necessary to be able to
// concat url parts in a correct manner. We can not use path.Join here,
// because this always trims the trailing slash, which causes the
// Do function to always run into 301 and then retry the correct
// Location. With GET, it does work with one useless request, but it breaks
// each other http method.
// Doing this, because out-of-tree extensions are more robust. If someone
// implements an own API-call, we do not override parts of c.u, even if
// the caller uses "/api/...".
rel, err := url.Parse(strings.TrimLeft(endpoint, "/"))
if err != nil {
return nil, err
}
u := c.u.ResolveReference(rel)
// If no valuer specified, create a request with no query parameters
if options == nil {
return http.NewRequest(method, u.String(), nil)
return http.NewRequest(method, u.String(), body)
}
values, err := options.Values()
......@@ -95,7 +123,41 @@ func (c *Client) NewRequest(method string, endpoint string, options Valuer) (*ht
}
u.RawQuery = values.Encode()
return http.NewRequest(method, u.String(), nil)
return http.NewRequest(method, u.String(), body)
}
// NewJSONRequest creates a HTTP request using the input HTTP method, URL
// endpoint, a Valuer which creates URL parameters for the request, and
// an io.Reader as the body of the request.
//
// If a nil Valuer is specified, no query parameters will be sent with the
// request.
//
// The body parameter is marshaled to JSON and sent as a HTTP request body.
// Body must not be nil.
func (c *Client) NewJSONRequest(method string, endpoint string, options Valuer, body interface{}) (*http.Request, error) {
if body == nil {
return nil, errors.New("expected body to be not nil")
}
b := new(bytes.Buffer)
err := json.NewEncoder(b).Encode(body)
if err != nil {
return nil, err
}
req, err := c.NewDataRequest(
method,
endpoint,
options,
b,
)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/json; charset=utf-8")
return req, nil
}
// Do executes an HTTP request and if v is not nil, Do unmarshals result
......
......@@ -18,6 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
......@@ -95,6 +96,46 @@ func TestClientBadStatusCode(t *testing.T) {
}
}
func TestNewJSONRequest(t *testing.T) {
c, done := testClient(t, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foo"))
})
defer done()
wantBody := "{\"id\":1,\"name\":\"Test 1\"}\n"
wantHeader := "application/json; charset=utf-8"
req, err := c.NewJSONRequest(http.MethodPost, "/", nil, &struct {
ID int `json:"id"`
Name string `json:"name"`
}{
ID: 1,
Name: "Test 1",
})
if err != nil {
t.Fatal("expected no error, but an error returned")
}
res, err := ioutil.ReadAll(req.Body)
if err != nil {
t.Fatal("expected no error, but an error returned")
}
if want, got := wantBody, string(res); got != want {
t.Fatalf("unexpected body:\n- want: %v\n- got: %v", want, got)
}
if want, got := wantHeader, req.Header.Get("Content-Type"); got != want {
t.Fatalf("unexpected body:\n- want: %v\n- got: %v", want, got)
}
req, err = c.NewJSONRequest(http.MethodPost, "/", nil, nil)
if err == nil {
t.Fatal("expected an error, but there was none")
}
if req != nil {
t.Fatalf("expected a nil request, but got %v", req)
}
}
func TestClientQueryParameters(t *testing.T) {
c := &Client{
u: &url.URL{},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment