Skip to content

Commit

Permalink
REPLY-X_Create first iteration of library (#1)
Browse files Browse the repository at this point in the history
* 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
ooaklee and Leon Silcott authored Aug 28, 2021
1 parent c6b8549 commit 75ae709
Show file tree
Hide file tree
Showing 9 changed files with 961 additions and 1 deletion.
10 changes: 10 additions & 0 deletions .github/pull_request_template.md
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:

104 changes: 103 additions & 1 deletion README.md
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"}
]
}
```
180 changes: 180 additions & 0 deletions examples/example_simple_api.go
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()
}
11 changes: 11 additions & 0 deletions go.mod
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
)
11 changes: 11 additions & 0 deletions go.sum
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=
Loading

0 comments on commit 75ae709

Please sign in to comment.