Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added read, list, search methods for numbers API #67

Merged
merged 15 commits into from
Aug 15, 2019
156 changes: 156 additions & 0 deletions number/number.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package number

import (
"fmt"
"net/http"
"net/url"
"regexp"
"strconv"

messagebird "github.com/messagebird/go-rest-api"
)

const (
apiRoot = "https://numbers.messagebird.com/v1"
pathNumbers = "phone-numbers"
pathNumbersAvailable = "available-phone-numbers"
)

type Number struct {
Number string
Country string
Region string
Locality string
Features []string
Tags []string
Type string
Status string
}

type NumberList struct {
Offset int
Limit int
Count int
TotalCount int
Items []*Number
}

type NumberSearchingList struct {
Items []*Number
Limit int
Count int
}

type NumberListParams struct {
Limit int
Offset int
Number string
Country string
Region string
Locality string
Features []string
Type []string
epels marked this conversation as resolved.
Show resolved Hide resolved
Status string
SearchPattern string
This conversation was marked as resolved.
Show resolved Hide resolved
}

// request does the exact same thing as Client.Request. It does, however,
// prefix the path with the Numbers API's root. This ensures the client
// doesn't "handle" this for us: by default, it uses the REST API.
func request(c *messagebird.Client, v interface{}, method, path string, data interface{}) error {
fmt.Println(path)
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
return c.Request(v, method, fmt.Sprintf("%s/%s", apiRoot, path), data)
}

// List get all purchased phone numbers
func List(c *messagebird.Client, listParams *NumberListParams) (*NumberList, error) {
uri := GetPath(listParams, pathNumbers)

numberList := &NumberList{}
if err := request(c, numberList, http.MethodGet, uri, nil); err != nil {
return nil, err
}
return numberList, nil
}

// Search for phone numbers available for purchase
func Search(c *messagebird.Client, cc string, listParams *NumberListParams) (*NumberSearchingList, error) {
This conversation was marked as resolved.
Show resolved Hide resolved
uri := GetPath(listParams, pathNumbersAvailable+"/"+cc)

numberList := &NumberSearchingList{}
if err := request(c, numberList, http.MethodGet, uri, nil); err != nil {
return nil, err
}

return numberList, nil
}

// Read get a purchased phone number
func Read(c *messagebird.Client, phoneNumber string) (*Number, error) {
if len(phoneNumber) < 5 {
return nil, fmt.Errorf("a phoneNumber is too short")
}

uri := fmt.Sprintf("%s/%s", pathNumbers, phoneNumber)

number := &Number{}
if err := request(c, number, http.MethodGet, uri, nil); err != nil {
return nil, err
}

return number, nil
}

// GetPath get the full path for the request
func GetPath(listParams *NumberListParams, path string) string {
This conversation was marked as resolved.
Show resolved Hide resolved
This conversation was marked as resolved.
Show resolved Hide resolved
params := paramsForMessageList(listParams)
return fmt.Sprintf("%s?%s", path, params.Encode())
}

// paramsForMessageList build query params
func paramsForMessageList(params *NumberListParams) *url.Values {
urlParams := &url.Values{}

if params == nil {
return urlParams
}

if len(params.Features) > 0 {
paramsForArrays("features", "^(sms|voice|mms)$", params.Features, urlParams)
}

if len(params.Type) > 0 {
paramsForArrays("type", "^(mobile|mobile|premium_rate)$", params.Type, urlParams)
This conversation was marked as resolved.
Show resolved Hide resolved
}

if params.Number != "" {
urlParams.Set("number", params.Number)
}
if params.Country != "" {
urlParams.Set("country", params.Country)
}
if params.Limit != 0 {
urlParams.Set("limit", strconv.Itoa(params.Limit))
}

if params.SearchPattern != "" {
urlParams.Set("search_pattern", params.SearchPattern)
}

if params.Offset != 0 {
urlParams.Set("offset", strconv.Itoa(params.Offset))
}

return urlParams
}

// paramsForArrays build query for array params
func paramsForArrays(field string, pattern string, array []string, urlParams *url.Values) {
r, _ := regexp.Compile(pattern)
This conversation was marked as resolved.
Show resolved Hide resolved

This conversation was marked as resolved.
Show resolved Hide resolved
for i := 0; i < len(array); i++ {
if match := r.MatchString(array[i]); match {
urlParams.Add(field, array[i])
}
}
}
72 changes: 72 additions & 0 deletions number/number_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package number

import (
"net/http"
"testing"

"github.com/messagebird/go-rest-api/internal/mbtest"
)

func TestMain(m *testing.M) {
mbtest.EnableServer(m)
}

func TestSearch(t *testing.T) {
mbtest.WillReturnTestdata(t, "numberSearch.json", http.StatusOK)
client := mbtest.Client(t)

numLis, err := Search(client, "NL", &NumberListParams{Limit: 10, Features: []string{"sms", "voice"}, Type: []string{"mobile"}})
if err != nil {
t.Fatalf("unexpected error searching Numbers: %s", err)
}

if numLis.Limit > 100 {
This conversation was marked as resolved.
Show resolved Hide resolved
t.Fatalf("got %d, expected <= 100", numLis.Limit)
}

if numLis.Items[0].Country != "NL" {
t.Fatalf("got %s, expected NL", numLis.Items[0].Country)
}

mbtest.AssertEndpointCalled(t, http.MethodGet, "/v1/available-phone-numbers/NL")

if query := mbtest.Request.URL.RawQuery; query != "features=sms&features=voice&limit=10&type=mobile" {
t.Fatalf("got %s, expected features=sms&features=voice&limit=10", query)
}
}

func TestList(t *testing.T) {
mbtest.WillReturnTestdata(t, "numberList.json", http.StatusOK)
client := mbtest.Client(t)

numLis, err := List(client, &NumberListParams{Limit: 10})
if err != nil {
t.Fatalf("unexpected error searching Numbers: %s", err)
}

if numLis.Items[0].Country != "NL" {
t.Fatalf("got %s, expected NL", numLis.Items[0].Country)
}

mbtest.AssertEndpointCalled(t, http.MethodGet, "/v1/phone-numbers")

if query := mbtest.Request.URL.RawQuery; query != "limit=10" {
t.Fatalf("got %s, expected limit=10", query)
}
}

func TestRead(t *testing.T) {
mbtest.WillReturnTestdata(t, "numberRead.json", http.StatusOK)
client := mbtest.Client(t)

num, err := Read(client, "31612345670")
if err != nil {
t.Fatalf("unexpected error searching Numbers: %s", err)
}

if num.Number != "31612345670" {
t.Fatalf("got %s, expected 31612345670", num.Number)
}

mbtest.AssertEndpointCalled(t, http.MethodGet, "/v1/phone-numbers/31612345670")
}
21 changes: 21 additions & 0 deletions number/testdata/numberList.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"offset": 0,
"limit": 20,
"count": 1,
"totalCount": 1,
"items": [
{
"number": "31612345670",
"country": "NL",
"region": "Texel",
"locality": "Texel",
"features": [
"sms",
"voice"
],
"tags": [],
"type": "mobile",
"status": "active"
}
]
}
13 changes: 13 additions & 0 deletions number/testdata/numberRead.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"number": "31612345670",
"country": "NL",
"region": "Texel",
"locality": "Texel",
"features": [
"sms",
"voice"
],
"tags": [],
"type": "mobile",
"status": "active"
}
14 changes: 14 additions & 0 deletions number/testdata/numberSearch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"items": [
{
"number": "3197010260188",
"country": "NL",
"region": "",
"locality": "",
"features": ["sms", "voice"],
"type": "mobile"
}
],
"limit": 20,
"count": 1
}