Skip to content

Commit

Permalink
Merge pull request #1 from arcanericky/feature/context
Browse files Browse the repository at this point in the history
Context and improved response field handling
  • Loading branch information
arcanericky authored Jul 17, 2019
2 parents dd5293c + 65512cc commit ecd2816
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 152 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ Go package for the [Pushover](https://pushover.net/) API.

[![Build Status](https://travis-ci.com/arcanericky/pushover.svg?branch=master)](https://travis-ci.com/arcanericky/pushover)
[![codecov](https://codecov.io/gh/arcanericky/pushover/branch/master/graph/badge.svg)](https://codecov.io/gh/arcanericky/pushover)
[![GoDoc](https://img.shields.io/badge/docs-GoDoc-brightgreen.svg)](https://godoc.org/github.com/arcanericky/pushover)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)

## About

This package enables your Go application to send requests to the [Pushover](https://pushover.net/) service through the [Pushover REST API](https://pushover.net/api). Implementing a notification message in your Go code is as simple as `pushover.Message(pushover.MessageRequest{User: "user", Token: "token", Message: "message"}`. It's just a straightforward function call - no fancy methods attached to functions, just populate a structure and [Go](https://golang.org/).
This package enables your Go application to send requests to the [Pushover](https://pushover.net/) service through the [Pushover REST API](https://pushover.net/api). Implementing a notification message in your Go code is as simple as `pushover.Message(pushover.MessageRequest{User: "user", Token: "token", Message: "message"})`. It's just a straightforward function call - no fancy methods attached to functions, just populate a structure and [Go](https://golang.org/).

Note that Pushover has many APIs available, but currently this package only supports:
- [Messages](https://pushover.net/api#messages)
Expand All @@ -28,6 +29,7 @@ $ cat > main.go << EOF
package main
import (
"context"
"fmt"
"os"
"github.com/arcanericky/pushover"
Expand Down Expand Up @@ -129,8 +131,9 @@ Some features that are not implemented but would be welcome:
- [Groups](https://pushover.net/api/groups)
- [Glances](https://pushover.net/api/groups)
- [Licensing](https://pushover.net/api/licensing)
- [Open Client API](https://pushover.net/api/client)
- Use of environment variables for API and User tokens in the CLI
- [Open Client](https://pushover.net/api/client)
- [Limits](https://pushover.net/api#limits)
- Use of environment variables for API token in the CLI

## Inspiration

Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/arcanericky/pushover

go 1.12

require github.com/spf13/cobra v0.0.5
require (
github.com/spf13/cobra v0.0.5
golang.org/x/net v0.0.0-20190628185345-da137c7871d7
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
55 changes: 37 additions & 18 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pushover

import (
"bytes"
"context"
"encoding/json"
"io"
"mime/multipart"
Expand Down Expand Up @@ -128,7 +129,7 @@ type MessageResponse struct {
ErrorParameters map[string]string
}

func messageWithoutValidation(request MessageRequest) (*MessageResponse, error) {
func messageWithoutValidation(ctx context.Context, request MessageRequest) (*MessageResponse, error) {
var requestData io.Reader
var contentType string

Expand Down Expand Up @@ -190,17 +191,21 @@ func messageWithoutValidation(request MessageRequest) (*MessageResponse, error)
return nil, ErrInvalidRequest
}

req = req.WithContext(ctx)
req.Header.Set("Content-Type", contentType)

client := &http.Client{}
resp, err := client.Do(req)
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}

if err != nil {
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}

return nil, err
}
defer resp.Body.Close()

r := new(MessageResponse)

Expand Down Expand Up @@ -235,33 +240,30 @@ func messageWithoutValidation(request MessageRequest) (*MessageResponse, error)
delete(result, keyRequest)

// Populate errors
if r.Errors, err = interfaceArrayToStringArray(keyErrors, result); err != nil {
return nil, err
}
r.Errors = interfaceArrayToStringArray(keyErrors, result)
delete(result, keyErrors)

// Populate parameters with corresponding errors
if r.ErrorParameters, err = interfaceMapToStringMap(result); err != nil {
return nil, err
}
r.ErrorParameters = interfaceMapToStringMap(result)

return r, nil
}

// Message will submit a request to the Pushover
// MessageContext will submit a request to the Pushover
// Message API after validating the required fields
// are present. This function will send a
// message, triggering a notification on a user's
// device or a group's devices.
//
// The required fields are: Message, Token, User
//
// resp, err := pushover.Message(pushover.MessageRequest{
// Token: token,
// User: user,
// Message: message,
// resp, err := pushover.MessageContext(context.Background(),
// pushover.MessageRequest{
// Token: token,
// User: user,
// Message: message,
// })
func Message(request MessageRequest) (*MessageResponse, error) {
func MessageContext(ctx context.Context, request MessageRequest) (*MessageResponse, error) {
// Validate Message
if len(request.Message) == 0 {
return nil, ErrInvalidMessage
Expand All @@ -277,5 +279,22 @@ func Message(request MessageRequest) (*MessageResponse, error) {
return nil, ErrInvalidUser
}

return messageWithoutValidation(request)
return messageWithoutValidation(ctx, request)
}

// Message will submit a request to the Pushover
// Message API after validating the required fields
// are present. This function will send a
// message, triggering a notification on a user's
// device or a group's devices.
//
// The required fields are: Message, Token, User
//
// resp, err := pushover.Message(pushover.MessageRequest{
// Token: token,
// User: user,
// Message: message,
// })
func Message(request MessageRequest) (*MessageResponse, error) {
return MessageContext(context.Background(), request)
}
56 changes: 19 additions & 37 deletions message_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package pushover

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
)

/*
Expand Down Expand Up @@ -88,36 +90,22 @@ func serverHandler(w http.ResponseWriter, r *http.Request) {

if user[0] == "failstatus" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, `{"status":"abc","request":"%s"}`, id)
return
}

if user[0] == "failrequest" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, `{"status":1,"request":1337}`)
return
}

if user[0] == "failerrors" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"priority":"is invalid, can only be -2, -1, 0, 1, or 2","errors":[1337],"status":0,"request":"%s"}`, id)
return
}

if user[0] == "failparameters" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"priority":1337,"errors":["priority is invalid"],"status":0,"request":"%s"}`, id)
return
}

if user[0] == "failjson" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"priority":1337,"errors":"priority is invalid"],"status":0,"request":"%s"}`, id)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, `{"priority":"is invalid, can only be -2, -1, 0, 1, or 2","errors":"priority is invalid"],"status":0,"request":"%s"}`, id)
return
}

Expand All @@ -140,22 +128,22 @@ func TestPushoverMessage(t *testing.T) {

// Default Pushover URL
messagesURL = apiServer.URL
r, e := messageWithoutValidation(request)
r, e := messageWithoutValidation(context.TODO(), request)
if r.HTTPStatusCode != http.StatusBadRequest || r.APIStatus != 0 || r.Request != id ||
r.Errors[0] != "message cannot be blank" || r.ErrorParameters["message"] != "cannot be blank" {
t.Error("Default Pushover URL")
}

// Invalid Pushover URL
request.PushoverURL = "\x7f"
r, e = messageWithoutValidation(request)
r, e = messageWithoutValidation(context.TODO(), request)
if e != ErrInvalidRequest {
t.Error("Invalid Pushover URL")
}

// Handling of no message
request.PushoverURL = apiServer.URL
r, e = messageWithoutValidation(request)
r, e = messageWithoutValidation(context.TODO(), request)
if r.HTTPStatusCode != http.StatusBadRequest || r.APIStatus != 0 || r.Request != id ||
r.Errors[0] != "message cannot be blank" || r.ErrorParameters["message"] != "cannot be blank" {
t.Error("Handling of no message without validation")
Expand All @@ -168,7 +156,7 @@ func TestPushoverMessage(t *testing.T) {

// Handling of no token
request.Message = "test message"
r, e = messageWithoutValidation(request)
r, e = messageWithoutValidation(context.TODO(), request)
if r.HTTPStatusCode != http.StatusBadRequest || r.APIStatus != 0 || r.Request != id ||
r.Errors[0] != "application token is invalid" || r.ErrorParameters["token"] != "invalid" {
t.Error("Handling of no token without validation")
Expand All @@ -181,7 +169,7 @@ func TestPushoverMessage(t *testing.T) {

// Handling of no user
request.Token = "testtoken"
r, e = messageWithoutValidation(request)
r, e = messageWithoutValidation(context.TODO(), request)
if r.HTTPStatusCode != http.StatusBadRequest || r.APIStatus != 0 || r.Request != id ||
r.Errors[0] != "user identifier is not a valid user, group, or subscribed user key" ||
r.ErrorParameters["user"] != "invalid" {
Expand Down Expand Up @@ -215,20 +203,6 @@ func TestPushoverMessage(t *testing.T) {
t.Error("Invalid request ID in response")
}

// Invalid errors array in response
request.User = "failerrors"
r, e = Message(request)
if e != ErrInvalidResponse {
t.Error("Invalid errors list in response")
}

// Invalid parameters array in response
request.User = "failparameters"
r, e = Message(request)
if e != ErrInvalidResponse {
t.Error("Invalid error parameters in response")
}

// Invalid json response
request.User = "failjson"
r, e = Message(request)
Expand Down Expand Up @@ -260,6 +234,14 @@ func TestPushoverMessage(t *testing.T) {
t.Error("All fields submitted")
}

// Context cancellation
ctx, cancel := context.WithTimeout(context.Background(), 0*time.Millisecond)
r, e = MessageContext(ctx, request)
if e != context.DeadlineExceeded {
t.Error("Context deadline exceeded")
}
cancel()

// Image attachment
request.ImageReader = strings.NewReader("image data")
r, e = Message(request)
Expand Down
22 changes: 10 additions & 12 deletions pushover.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
// library functions.
package pushover

import "errors"
import (
"errors"
"fmt"
)

const (
keyDevice = "device"
Expand Down Expand Up @@ -63,34 +66,29 @@ func mapKeyToInt(key string, m map[string]interface{}) (int, bool) {
return result, ok
}

func interfaceArrayToStringArray(key string, m map[string]interface{}) ([]string, error) {
func interfaceArrayToStringArray(key string, m map[string]interface{}) []string {
var interfaceArray []interface{}
var stringArray []string
var ok bool

if interfaceArray, ok = m[key].([]interface{}); ok {
stringArray = make([]string, len(interfaceArray))
for i, v := range interfaceArray {
if stringArray[i], ok = v.(string); !ok {
return nil, ErrInvalidResponse
}
stringArray[i] = fmt.Sprintf("%v", v)
}
} else {
stringArray = []string{}
}

return stringArray, nil
return stringArray
}

func interfaceMapToStringMap(inMap map[string]interface{}) (map[string]string, error) {
var ok bool
func interfaceMapToStringMap(inMap map[string]interface{}) map[string]string {
outMap := make(map[string]string)

for k, v := range inMap {
if outMap[k], ok = v.(string); !ok {
return nil, ErrInvalidResponse
}
outMap[k] = fmt.Sprintf("%v", v)
}

return outMap, nil
return outMap
}
Loading

0 comments on commit ecd2816

Please sign in to comment.