Skip to content

Commit e27b3ad

Browse files
author
Derek Dowling
committed
Isolated client logic into jsc package, added Parser type to avoid namespace polution and to simplify parsing DSL
1 parent b9fe452 commit e27b3ad

23 files changed

+508
-399
lines changed

client.go

-170
This file was deleted.

client/client.go

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package jsc
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"net/url"
9+
"strconv"
10+
"strings"
11+
12+
"github.com/derekdowling/go-json-spec-handler"
13+
)
14+
15+
// Response is a wrapper around an http.Response that allows us to perform
16+
// intelligent actions on them
17+
type Response struct {
18+
*http.Response
19+
}
20+
21+
// GetObject validates the http response and parses out the JSON object from the
22+
// body if possible
23+
func (r *Response) GetObject() (*jsh.Object, error) {
24+
return buildParser(r).GetObject()
25+
}
26+
27+
// GetList validates the http response and parses out the JSON list from the
28+
// body if possible
29+
func (r *Response) GetList() (jsh.List, error) {
30+
return buildParser(r).GetList()
31+
}
32+
33+
// BodyStr is a convenience function that parses the body of the response into a
34+
// string BUT DOESN'T close the ReadCloser
35+
func (r *Response) BodyStr() (string, error) {
36+
37+
byteData, err := ioutil.ReadAll(r.Body)
38+
if err != nil {
39+
return "", fmt.Errorf("Error attempting to read request body: %s", err)
40+
}
41+
42+
return string(byteData), nil
43+
}
44+
45+
func buildParser(response *Response) *jsh.Parser {
46+
return &jsh.Parser{
47+
Method: "",
48+
Headers: response.Header,
49+
Payload: response.Body,
50+
}
51+
}
52+
53+
// setPath builds a JSON url.Path for a given resource type. Typically this just
54+
// envolves concatting a pluralized resource name
55+
func setPath(url *url.URL, resource string) {
56+
57+
if url.Path != "" && !strings.HasSuffix(url.Path, "/") {
58+
url.Path = url.Path + "/"
59+
}
60+
61+
url.Path = fmt.Sprintf("%s%ss", url.Path, resource)
62+
}
63+
64+
// setIDPath creates a JSON url.Path for a specific resource type including an
65+
// ID specifier.
66+
func setIDPath(url *url.URL, resource string, id string) {
67+
setPath(url, resource)
68+
url.Path = strings.Join([]string{url.Path, id}, "/")
69+
}
70+
71+
// objectToPayload first prepares/validates the object to ensure it is JSON
72+
// spec compatible, and then marshals it to JSON
73+
func objectToPayload(request *http.Request, object *jsh.Object) ([]byte, error) {
74+
75+
payload, err := object.Prepare(request, false)
76+
if err != nil {
77+
return nil, fmt.Errorf("Error preparing object: %s", err.Error())
78+
}
79+
80+
jsonContent, jsonErr := json.MarshalIndent(payload, "", " ")
81+
if jsonErr != nil {
82+
return nil, fmt.Errorf("Unable to prepare JSON content: %s", jsonErr)
83+
}
84+
85+
return jsonContent, nil
86+
}
87+
88+
// sendPayloadRequest is required for sending JSON payload related requests
89+
// because by default the http package does not set Content-Length headers
90+
func sendObjectRequest(request *http.Request, object *jsh.Object) (*Response, error) {
91+
92+
payload, err := objectToPayload(request, object)
93+
if err != nil {
94+
return nil, fmt.Errorf("Error converting object to JSON: %s", err.Error())
95+
}
96+
97+
// prepare payload and corresponding headers
98+
request.Body = jsh.CreateReadCloser(payload)
99+
request.Header.Add("Content-Type", jsh.ContentType)
100+
request.Header.Set("Content-Length", strconv.Itoa(len(payload)))
101+
102+
client := &http.Client{}
103+
response, err := client.Do(request)
104+
if err != nil {
105+
return nil, fmt.Errorf(
106+
"Error sending %s request: %s", request.Method, err.Error(),
107+
)
108+
}
109+
110+
return &Response{response}, nil
111+
}

client/client_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package jsc
2+
3+
import (
4+
"net/url"
5+
"testing"
6+
7+
"github.com/derekdowling/go-json-spec-handler"
8+
. "github.com/smartystreets/goconvey/convey"
9+
)
10+
11+
const testURL = "https://httpbin.org"
12+
13+
func TestClientRequest(t *testing.T) {
14+
15+
Convey("Client Tests", t, func() {
16+
17+
Convey("->setPath()", func() {
18+
url := &url.URL{Host: "test"}
19+
20+
Convey("should format properly", func() {
21+
setPath(url, "test")
22+
So(url.String(), ShouldEqual, "//test/tests")
23+
})
24+
25+
Convey("should respect an existing path", func() {
26+
url.Path = "admin"
27+
setPath(url, "test")
28+
So(url.String(), ShouldEqual, "//test/admin/tests")
29+
})
30+
})
31+
32+
Convey("->setIDPath()", func() {
33+
url := &url.URL{Host: "test"}
34+
35+
Convey("should format properly an id url", func() {
36+
setIDPath(url, "test", "1")
37+
So(url.String(), ShouldEqual, "//test/tests/1")
38+
})
39+
})
40+
41+
})
42+
}
43+
44+
func TestClientResponse(t *testing.T) {
45+
46+
Convey("Client Response Tests", t, func() {
47+
48+
Convey("->GetObject()", func() {
49+
50+
obj, objErr := jsh.NewObject("123", "test", map[string]string{"test": "test"})
51+
So(objErr, ShouldBeNil)
52+
response, err := mockObjectResponse(obj)
53+
So(err, ShouldBeNil)
54+
55+
Convey("should parse successfully", func() {
56+
respObj, err := response.GetObject()
57+
So(err, ShouldBeNil)
58+
So(respObj, ShouldNotBeNil)
59+
})
60+
61+
})
62+
63+
Convey("->GetList()", func() {
64+
65+
obj, objErr := jsh.NewObject("123", "test", map[string]string{"test": "test"})
66+
So(objErr, ShouldBeNil)
67+
68+
list := jsh.List{obj, obj}
69+
70+
response, err := mockListResponse(list)
71+
So(err, ShouldBeNil)
72+
73+
Convey("should parse successfully", func() {
74+
respObj, err := response.GetList()
75+
So(err, ShouldBeNil)
76+
So(respObj, ShouldNotBeNil)
77+
})
78+
})
79+
})
80+
}

0 commit comments

Comments
 (0)