-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
REPLY-X_Create first iteration of library (#1)
* Updated README * Added example of transfer object override, and simple API implementation * Refactored code to make future debugging easier * Start to utilise error returned from NewHTTPResponse * Added log entry for unfound manifest error item * Update release notes Co-authored-by: Leon Silcott <lnsilcott+blizzard]@gmail.com>
- Loading branch information
Showing
9 changed files
with
961 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
## What has been done: | ||
- | ||
|
||
## Checklist | ||
- [] Updated documentation (i.e., `README.md`) | ||
- [] Updated tests for added feature | ||
- [] Created pre-release(s) - `X` | ||
|
||
## How to test: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,111 @@ | ||
# reply | ||
|
||
`reply` is a Go library that supports developers with standardising the responses sent from their API service(s). It also allows users to predefine non-successful messages and their corresponding status code based on errors passed to the `replier`. | ||
`reply` is a Go library that supports developers with standardising the responses sent from their API service(s). It allows users to predefine non-successful messages and their corresponding status code based on errors manifest passed to the `replier`. | ||
|
||
## Installation | ||
|
||
```sh | ||
go get github.com/ooaklee/reply | ||
``` | ||
|
||
## Examples | ||
|
||
There are several ways you can integrate `reply` into your application. Below, you will find an example of how you can get the most out of this package. | ||
|
||
### How to create a `replier` | ||
|
||
```go | ||
// (Optional) Create a error manifest, to hold correlating error as string and it's manifest | ||
// item | ||
baseManifest := []reply.ErrorManifest{ | ||
{"example-404-error": reply.ErrorManifestItem{Message: "resource not found", StatusCode: http.StatusNotFound}}, | ||
} | ||
|
||
// Create replier to manage the responses going back to consumer(s) | ||
replier := reply.NewReplier(baseManifest) | ||
``` | ||
|
||
### How to send response(s) | ||
|
||
You can use `reply` for both successful and error based responses. | ||
|
||
> `NOTE` - When sending an error response, it is essential to make sure you populate the `replier`'s error manifest with the correct errors. Otherwise, a `500 - Internal Server Error` response will be sent back to the client by default if it cannot match the passed error with on in the manifest. | ||
#### Making use of error manifest | ||
|
||
```go | ||
|
||
// ExampleHandler handler to demostrate how to use package for error | ||
// response | ||
func ExampleHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
// Create error with value corresponding to one of the manifest's entry's key | ||
exampleErr := errors.New("example-404-error") | ||
|
||
|
||
// Pass error to replier's method to return predefined response, else | ||
// 500 | ||
_ := replier.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
Error: exampleErr, | ||
}) | ||
} | ||
``` | ||
|
||
When the endpoint linked to the handler above is called, you should see the following JSON response. | ||
|
||
> `NOTE` - The `baseManifest` was initially declared, and its item represents the response shown below. Although the status code is not shown in the response body, it to has been set accordingly and returned to the consumer. | ||
```JSON | ||
|
||
{ | ||
"status": { | ||
"message": "resource not found" | ||
} | ||
} | ||
``` | ||
|
||
#### Sending client successful response | ||
|
||
|
||
```go | ||
|
||
// ExampleGetAllHandler handler to demostrate how to use package for successful | ||
// response | ||
func ExampleGetAllHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
// building sample user model | ||
type user struct { | ||
ID int `json:"id"` | ||
Name string `json:"name"` | ||
} | ||
|
||
// emulate users pulled from repository | ||
mockedQueriedUsers := []user{ | ||
{ID: 1, Name: "John Doe"}, | ||
{ID: 2, Name: "Sam Smith"}, | ||
} | ||
|
||
|
||
// build and sent default formatted JSON response for consumption | ||
// by client | ||
_ := replier.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
Data: mockedUsers | ||
StatusCode: htttp.StatusOK | ||
}) | ||
} | ||
``` | ||
|
||
When the endpoint linked to the handler above is called, you should see the following JSON response. | ||
|
||
> `NOTE` - Unlike the error use case, successful requests expect the `StatusCode` to be defined when creating a successful response. If you do not provide a status code, 200 will be assumed. | ||
```JSON | ||
{ | ||
"data": [ | ||
{"id": 1, "name": "John Doe"}, | ||
{"id": 2, "name": "Sam Smith"} | ||
] | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"log" | ||
"net/http" | ||
|
||
"github.com/ooaklee/reply" | ||
) | ||
|
||
///////////////////////////////////////////////// | ||
/////// Custom Transition Object Example //////// | ||
// This is an example of how you can create a | ||
// custom response structure based on your | ||
// requirements. | ||
|
||
type fooReplyTransferObject struct { | ||
HTTPWriter http.ResponseWriter `json:"-"` | ||
Headers map[string]string `json:"-"` | ||
StatusCode int `json:"-"` | ||
Bar barEmbeddedExample `json:"bar,omitempty"` | ||
} | ||
|
||
type barEmbeddedExample struct { | ||
Status *reply.TransferObjectStatus `json:"status,omitempty"` | ||
Meta map[string]interface{} `json:"meta,omitempty"` | ||
Data interface{} `json:"data,omitempty"` | ||
AccessToken string `json:"access_token,omitempty"` | ||
RefreshToken string `json:"refresh_token,omitempty"` | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetHeaders(headers map[string]string) { | ||
t.Headers = headers | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetStatusCode(code int) { | ||
t.StatusCode = code | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetMeta(meta map[string]interface{}) { | ||
t.Bar.Meta = meta | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetWriter(writer http.ResponseWriter) { | ||
t.HTTPWriter = writer | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetAccessToken(token string) { | ||
t.Bar.AccessToken = token | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetRefreshToken(token string) { | ||
t.Bar.RefreshToken = token | ||
} | ||
|
||
func (t *fooReplyTransferObject) GetWriter() http.ResponseWriter { | ||
return t.HTTPWriter | ||
} | ||
|
||
func (t *fooReplyTransferObject) GetStatusCode() int { | ||
return t.StatusCode | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetData(data interface{}) { | ||
t.Bar.Data = data | ||
} | ||
|
||
func (t *fooReplyTransferObject) RefreshTransferObject() reply.TransferObject { | ||
return &fooReplyTransferObject{} | ||
} | ||
|
||
func (t *fooReplyTransferObject) SetStatus(transferObjectStatus *reply.TransferObjectStatus) { | ||
t.Bar.Status = transferObjectStatus | ||
} | ||
|
||
//////////////////// | ||
|
||
type user struct { | ||
ID int `json:"id"` | ||
Name string `json:"name"` | ||
} | ||
|
||
var baseManifest []reply.ErrorManifest = []reply.ErrorManifest{ | ||
{"example-404-error": reply.ErrorManifestItem{Message: "resource not found", StatusCode: http.StatusNotFound}}, | ||
} | ||
|
||
var replier *reply.Replier = reply.NewReplier(baseManifest) | ||
|
||
var replierWithCustomTransitionObj *reply.Replier = reply.NewReplier(baseManifest, reply.WithTransferObject(&fooReplyTransferObject{})) | ||
|
||
func simpleUsersAPINotFoundHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
// Do something with a server | ||
serverErr := errors.New("example-404-error") | ||
|
||
replier.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
Error: serverErr, | ||
}) | ||
} | ||
|
||
func simpleUsersAPIHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
mockedQueriedUsers := []user{ | ||
{ID: 1, Name: "John Doe"}, | ||
{ID: 2, Name: "Sam Smith"}, | ||
} | ||
|
||
replier.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
Data: mockedQueriedUsers, | ||
}) | ||
} | ||
|
||
func simpleUsersAPINoManifestEntryHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
// unregisterdErr an error that's unregistered in manifest | ||
// should return 500 | ||
unregisterdErr := errors.New("unexpected-error") | ||
|
||
// mock passing additional headers in request | ||
mockAdditionalHeaders := map[string]string{ | ||
"correlation-id": "d7c09ac2-fa46-4ece-bcde-1d7ad81d2230", | ||
} | ||
|
||
replier.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
Error: unregisterdErr, | ||
Headers: mockAdditionalHeaders, | ||
}) | ||
} | ||
|
||
func simpleTokensAPIHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
mockedAccessToken := "05e42c11-8bdd-423d-a2c1-c3c5c6604a30" | ||
mockedRefreshToken := "0e95c426-d373-41a5-bfe1-08db322527bd" | ||
|
||
replier.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
AccessToken: mockedAccessToken, | ||
RefreshToken: mockedRefreshToken, | ||
}) | ||
} | ||
|
||
func simpleAPIDefaultResponseHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
// Do something that only needs an empty response body, and 200 status code | ||
replier.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
}) | ||
} | ||
|
||
func simpleUsersAPINotFoundCustomReplierHandler(w http.ResponseWriter, r *http.Request) { | ||
|
||
// Do something with a server | ||
serverErr := errors.New("example-404-error") | ||
|
||
replierWithCustomTransitionObj.NewHTTPResponse(&reply.NewResponseRequest{ | ||
Writer: w, | ||
Error: serverErr, | ||
}) | ||
} | ||
|
||
func handleRequest() { | ||
var port string = ":8081" | ||
|
||
http.HandleFunc("/users", simpleUsersAPIHandler) | ||
http.HandleFunc("/users/3", simpleUsersAPINotFoundHandler) | ||
http.HandleFunc("/users/4", simpleUsersAPINoManifestEntryHandler) | ||
http.HandleFunc("/tokens/refresh", simpleTokensAPIHandler) | ||
http.HandleFunc("/defaults/1", simpleAPIDefaultResponseHandler) | ||
http.HandleFunc("/custom/users/3", simpleUsersAPINotFoundCustomReplierHandler) | ||
|
||
log.Printf("Serving simple API on port %s...", port) | ||
log.Fatal(http.ListenAndServe(port, nil)) | ||
} | ||
|
||
func main() { | ||
handleRequest() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module github.com/ooaklee/reply | ||
|
||
go 1.17 | ||
|
||
require github.com/stretchr/testify v1.7.0 | ||
|
||
require ( | ||
github.com/davecgh/go-spew v1.1.0 // indirect | ||
github.com/pmezard/go-difflib v1.0.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
Oops, something went wrong.