From fde91abea4e707ce31e20796ad7d43c6ebc470b1 Mon Sep 17 00:00:00 2001 From: Derek Dowling Date: Tue, 5 Jan 2016 12:38:58 -0800 Subject: [PATCH] Adding support for intermediate client requests in jsc, allows custom headers to be set before sending Adding missing stdlogger Adding missing stdlogger --- Godeps/Godeps.json | 4 ++ .../derekdowling/go-stdlogger/.gitignore | 24 ++++++++++ .../derekdowling/go-stdlogger/LICENSE | 22 +++++++++ .../derekdowling/go-stdlogger/README.md | 24 ++++++++++ .../derekdowling/go-stdlogger/logger.go | 24 ++++++++++ client/client.go | 48 +++++++++---------- client/delete.go | 28 +++++++---- client/delete_test.go | 20 +++++++- client/patch.go | 23 +++++++-- client/patch_test.go | 2 - client/post.go | 22 +++++++-- 11 files changed, 198 insertions(+), 43 deletions(-) create mode 100644 Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/.gitignore create mode 100644 Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/LICENSE create mode 100644 Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/README.md create mode 100644 Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/logger.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 5f510d1..e181b99 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -10,6 +10,10 @@ "Comment": "v2-37-gedd46cd", "Rev": "edd46cdac249b001c7b7d88c6d43993ea875e8d8" }, + { + "ImportPath": "github.com/derekdowling/go-stdlogger", + "Rev": "cfa80b78b82f8d241ceece0b733b1161bb6a885c" + }, { "ImportPath": "github.com/derekdowling/goji2-logger", "Comment": "0.1-7-gad5b694", diff --git a/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/.gitignore b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/LICENSE b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/LICENSE new file mode 100644 index 0000000..f41ed91 --- /dev/null +++ b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Derek Dowling + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/README.md b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/README.md new file mode 100644 index 0000000..2f114f4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/README.md @@ -0,0 +1,24 @@ +# go-stdlogger + +The Go Standard Logging Interface. Plain and simple. + +```go +// Logger describes a logger interface that is compatible with the standard +// log.Logger but also logrus and others. As not to limit which loggers can and +// can't be used with the API. +// +// This interface is from https://godoc.org/github.com/Sirupsen/logrus#StdLogger +type Logger interface { + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) + + Fatal(...interface{}) + Fatalf(string, ...interface{}) + Fatalln(...interface{}) + + Panic(...interface{}) + Panicf(string, ...interface{}) + Panicln(...interface{}) +} +``` diff --git a/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/logger.go b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/logger.go new file mode 100644 index 0000000..aef3d10 --- /dev/null +++ b/Godeps/_workspace/src/github.com/derekdowling/go-stdlogger/logger.go @@ -0,0 +1,24 @@ +// Package std is the missing standard logging interface that should be present within +// Go. This entire package exposes a single "Logger" interface so that I don't +// need to pull in a huge dependency tree everytime I want to use this +// interface. +package std + +// Logger describes a logger interface that is compatible with the standard +// log.Logger but also logrus and others. As not to limit which loggers can and +// can't be used with the API. +// +// This interface is from https://godoc.org/github.com/Sirupsen/logrus#StdLogger +type Logger interface { + Print(...interface{}) + Printf(string, ...interface{}) + Println(...interface{}) + + Fatal(...interface{}) + Fatalf(string, ...interface{}) + Fatalln(...interface{}) + + Panic(...interface{}) + Panicf(string, ...interface{}) + Panicln(...interface{}) +} diff --git a/client/client.go b/client/client.go index f7d0a59..36062c7 100644 --- a/client/client.go +++ b/client/client.go @@ -72,49 +72,49 @@ func setIDPath(url *url.URL, resource string, id string) { } } -// objectToPayload first prepares/validates the object to ensure it is JSON -// spec compatible, and then marshals it to JSON -func objectToPayload(request *http.Request, object *jsh.Object) ([]byte, *jsh.Error) { +// prepareBody first prepares/validates the object to ensure it is JSON +// spec compatible, and then marshals it to JSON, sets the request body and +// corresponding attributes +func prepareBody(request *http.Request, object *jsh.Object) error { err := object.Validate(request, false) if err != nil { - return nil, jsh.ISE(fmt.Sprintf("Error preparing object: %s", err.Error())) + return fmt.Errorf("Error preparing object: %s", err.Error()) } doc := jsh.Build(object) - jsonContent, jsonErr := json.MarshalIndent(doc, "", " ") + jsonContent, jsonErr := json.MarshalIndent(doc, "", " ") if jsonErr != nil { - return nil, jsh.ISE(fmt.Sprintf("Unable to prepare JSON content: %s", jsonErr)) + return fmt.Errorf("Unable to prepare JSON content: %s", jsonErr.Error()) } - return jsonContent, nil -} - -// sendPayloadRequest is required for sending JSON payload related requests -// because by default the http package does not set Content-Length headers -func doObjectRequest(request *http.Request, object *jsh.Object) (*jsh.Document, *http.Response, *jsh.Error) { + request.Body = jsh.CreateReadCloser(jsonContent) + request.ContentLength = int64(len(jsonContent)) - payload, err := objectToPayload(request, object) - if err != nil { - return nil, nil, jsh.ISE(fmt.Sprintf("Error converting object to JSON: %s", err.Error())) - } + return nil +} - // prepare payload and corresponding headers - request.Body = jsh.CreateReadCloser(payload) - request.Header.Add("Content-Type", jsh.ContentType) +// Do sends a the specified request to a JSON API compatible endpoint and +// returns the resulting JSON Document if possible along with the response, +// and any errors that were encountered while sending, or parsing the +// JSON Document. +func Do(request *http.Request) (*jsh.Document, *http.Response, error) { - contentLength := strconv.Itoa(len(payload)) - request.ContentLength = int64(len(payload)) - request.Header.Set("Content-Length", contentLength) + request.Header.Set("Content-Type", jsh.ContentType) + request.Header.Set("Content-Length", strconv.Itoa(int(request.ContentLength))) client := &http.Client{} httpResponse, clientErr := client.Do(request) if clientErr != nil { - return nil, nil, jsh.ISE(fmt.Sprintf( + return nil, nil, fmt.Errorf( "Error sending %s request: %s", request.Method, clientErr.Error(), - )) + ) + } + + if request.Method == "DELETE" { + return nil, httpResponse, nil } document, err := Document(httpResponse) diff --git a/client/delete.go b/client/delete.go index 7c1d27c..5760552 100644 --- a/client/delete.go +++ b/client/delete.go @@ -8,12 +8,28 @@ import ( "github.com/derekdowling/go-json-spec-handler" ) -// Delete allows a user to make an outbound DELETE /resources/:id request: +// Delete allows a user to make an outbound "DELETE /resource/:id" request. // // resp, err := jsh.Delete("http://apiserver", "user", "2") // -func Delete(urlStr string, resourceType string, id string) (*http.Response, *jsh.Error) { +func Delete(urlStr string, resourceType string, id string) (*http.Response, error) { + request, err := DeleteRequest(urlStr, resourceType, id) + if err != nil { + return nil, err + } + + _, response, err := Do(request) + if err != nil { + return nil, err + } + + return response, nil +} +// DeleteRequest returns a fully formatted request for performing a JSON API DELETE. +// This is useful for if you need to set custom headers on the request. Otherwise +// just use "jsc.Delete". +func DeleteRequest(urlStr string, resourceType string, id string) (*http.Request, error) { u, err := url.Parse(urlStr) if err != nil { return nil, jsh.ISE(fmt.Sprintf("Error parsing URL: %s", err.Error())) @@ -27,11 +43,5 @@ func Delete(urlStr string, resourceType string, id string) (*http.Response, *jsh return nil, jsh.ISE(fmt.Sprintf("Error creating DELETE request: %s", err.Error())) } - client := &http.Client{} - response, err := client.Do(request) - if err != nil { - return nil, jsh.ISE(fmt.Sprintf("Error sending DELETE request: %s", err.Error())) - } - - return response, nil + return request, nil } diff --git a/client/delete_test.go b/client/delete_test.go index 1b4e707..3dc1813 100644 --- a/client/delete_test.go +++ b/client/delete_test.go @@ -1,6 +1,9 @@ package jsc import ( + "log" + "net/http" + "net/http/httptest" "testing" . "github.com/smartystreets/goconvey/convey" @@ -8,8 +11,21 @@ import ( func TestDelete(t *testing.T) { - Convey("Delete Tests", t, func() { + Convey("DELETE Tests", t, func() { - }) + api := testAPI() + server := httptest.NewServer(api) + defer server.Close() + + baseURL := server.URL + Convey("->Delete()", func() { + resp, err := Delete(baseURL, "test", "1") + + So(err, ShouldBeNil) + log.Printf("patchErr = %+v\n", err) + log.Printf("resp = %+v\n", resp) + So(resp.StatusCode, ShouldEqual, http.StatusOK) + }) + }) } diff --git a/client/patch.go b/client/patch.go index 60fdbbf..2494256 100644 --- a/client/patch.go +++ b/client/patch.go @@ -17,17 +17,34 @@ import ( // updatedObj := json.First() // func Patch(baseURL string, object *jsh.Object) (*jsh.Document, *http.Response, error) { + request, err := PatchRequest(baseURL, object) + if err != nil { + return nil, nil, err + } + + return Do(request) +} + +// PatchRequest returns a fully formatted request with JSON body for performing +// a JSONAPI PATCH. This is useful for if you need to set custom headers on the +// request. Otherwise just use "jsc.Patch". +func PatchRequest(baseURL string, object *jsh.Object) (*http.Request, error) { u, err := url.Parse(baseURL) if err != nil { - return nil, nil, fmt.Errorf("Error parsing URL: %s", err.Error()) + return nil, fmt.Errorf("Error parsing URL: %s", err.Error()) } setIDPath(u, object.Type, object.ID) request, err := http.NewRequest("PATCH", u.String(), nil) if err != nil { - return nil, nil, fmt.Errorf("Error creating PATCH request: %s", err.Error()) + return nil, fmt.Errorf("Error creating PATCH request: %s", err.Error()) + } + + err = prepareBody(request, object) + if err != nil { + return nil, err } - return doObjectRequest(request, object) + return request, nil } diff --git a/client/patch_test.go b/client/patch_test.go index a92ed50..6caaeb6 100644 --- a/client/patch_test.go +++ b/client/patch_test.go @@ -1,7 +1,6 @@ package jsc import ( - "log" "net/http" "net/http/httptest" "testing" @@ -25,7 +24,6 @@ func TestPatch(t *testing.T) { So(err, ShouldBeNil) json, resp, patchErr := Patch(baseURL, object) - log.Printf("resp.Request = %+v\n", resp.Request) So(resp.StatusCode, ShouldEqual, http.StatusOK) So(patchErr, ShouldBeNil) diff --git a/client/post.go b/client/post.go index 400c744..6a82616 100644 --- a/client/post.go +++ b/client/post.go @@ -14,10 +14,21 @@ import ( // // does POST http://apiserver/user/123 // json, resp, err := jsh.Post("http://apiserver", obj) func Post(baseURL string, object *jsh.Object) (*jsh.Document, *http.Response, error) { + request, err := PostRequest(baseURL, object) + if err != nil { + return nil, nil, err + } + + return Do(request) +} +// PostRequest returns a fully formatted request with JSON body for performing +// a JSONAPI POST. This is useful for if you need to set custom headers on the +// request. Otherwise just use "jsc.Post". +func PostRequest(baseURL string, object *jsh.Object) (*http.Request, error) { u, err := url.Parse(baseURL) if err != nil { - return nil, nil, fmt.Errorf("Error parsing URL: %s", err.Error()) + return nil, fmt.Errorf("Error parsing URL: %s", err.Error()) } // ghetto pluralization, fix when it becomes an issue @@ -25,8 +36,13 @@ func Post(baseURL string, object *jsh.Object) (*jsh.Document, *http.Response, er request, err := http.NewRequest("POST", u.String(), nil) if err != nil { - return nil, nil, fmt.Errorf("Error building POST request: %s", err.Error()) + return nil, fmt.Errorf("Error building POST request: %s", err.Error()) + } + + err = prepareBody(request, object) + if err != nil { + return nil, err } - return doObjectRequest(request, object) + return request, nil }