Skip to content

Commit

Permalink
DomainList() now returns an iterator
Browse files Browse the repository at this point in the history
* Updated godoc documentation
* Removed more deprecated types
* Renamed ApiBase to APIBase
* Removed gobuffalo/envy dependency
* Updated copyright to 2019
  • Loading branch information
thrawn01 committed Jan 14, 2019
1 parent 883d51d commit 9651329
Show file tree
Hide file tree
Showing 29 changed files with 379 additions and 527 deletions.
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
.PHONY: all
.DEFAULT_GOAL := all

PACKAGE := github.com/mailgun/mailgun-go

gen:
rm events/events_easyjson.go
easyjson --all events/events.go
Expand All @@ -9,3 +11,11 @@ gen:

all:
export GO111MODULE=on; go test . -v

godoc:
mkdir -p /tmp/tmpgoroot/doc
-rm -rf /tmp/tmpgopath/src/${PACKAGE}
mkdir -p /tmp/tmpgopath/src/${PACKAGE}
tar -c --exclude='.git' --exclude='tmp' . | tar -x -C /tmp/tmpgopath/src/${PACKAGE}
echo -e "open http://localhost:6060/pkg/${PACKAGE}\n"
GOROOT=/tmp/tmpgoroot/ GOPATH=/tmp/tmpgopath/ godoc -http=localhost:6060
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Mailgun with Go

[![GoDoc](https://godoc.org/gopkg.in/mailgun/mailgun-go.v1?status.svg)](https://godoc.org/gopkg.in/mailgun/mailgun-go.v1)
[![GoDoc](https://godoc.org/github.com/mailgun/mailgun-go?status.svg)](https://godoc.org/github.com/mailgun/mailgun-go)
[![Build Status](https://img.shields.io/travis/mailgun/mailgun-go/master.svg)](https://travis-ci.org/mailgun/mailgun-go)

Go library for interacting with the [Mailgun](https://mailgun.com/) [API](https://documentation.mailgun.com/api_reference.html).

Expand Down
13 changes: 7 additions & 6 deletions bounces.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import (

// Bounce aggregates data relating to undeliverable messages to a specific intended recipient,
// identified by Address.
// Code provides the SMTP error code causing the bounce,
// while Error provides a human readable reason why.
// CreatedAt provides the time at which Mailgun detected the bounce.
type Bounce struct {
// The time at which Mailgun detected the bounce.
CreatedAt RFC2822Time `json:"created_at"`
Code string `json:"code"`
Address string `json:"address"`
Error string `json:"error"`
// Code provides the SMTP error code that caused the bounce
Code string `json:"code"`
// Address the bounce is for
Address string `json:"address"`
// human readable reason why
Error string `json:"error"`
}

type Paging struct {
Expand Down
2 changes: 1 addition & 1 deletion cmd/mailgun/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func main() {

parser := args.NewParser(args.EnvPrefix("MG_"), args.Desc(desc, args.IsFormated))
parser.AddOption("--verbose").Alias("-v").IsTrue().Help("be verbose")
parser.AddOption("--url").Env("URL").Default(mailgun.ApiBase).Help("url to the mailgun api")
parser.AddOption("--url").Env("URL").Default(mailgun.APIBase).Help("url to the mailgun api")
parser.AddOption("--api-key").Env("API_KEY").Help("mailgun api key")
parser.AddOption("--public-api-key").Env("PUBLIC_API_KEY").Help("mailgun public api key")
parser.AddOption("--domain").Env("DOMAIN").Help("mailgun domain name")
Expand Down
9 changes: 2 additions & 7 deletions credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ type Credential struct {
}

type credentialsListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Credential `json:"items"`
}

// ErrEmptyParam results occur when a required parameter is missing.
// Returned when a required parameter is missing.
var ErrEmptyParam = fmt.Errorf("empty or illegal parameter")

// ListCredentials returns the (possibly zero-length) list of credentials associated with your domain.
Expand Down Expand Up @@ -50,12 +51,6 @@ func (ri *CredentialsIterator) Err() error {
return ri.err
}

// Return the total number of items as returned by the mailgun API
// Returns -1 if Next() or First() have not been called
func (ri *CredentialsIterator) Count() int {
return ri.TotalCount
}

// Returns the current offset of the iterator
func (ri *CredentialsIterator) Offset() int {
return ri.offset
Expand Down
187 changes: 153 additions & 34 deletions domains.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,14 @@ import (
"strings"
)

// DefaultLimit and DefaultSkip instruct the SDK to rely on Mailgun's reasonable defaults for Paging settings.
// Use these to specify a spam action when creating a new domain.
const (
DefaultLimit = -1
DefaultSkip = -1
)

// Disabled, Tag, and Delete indicate spam actions.
// Disabled prevents Mailgun from taking any action on what it perceives to be spam.
// Tag instruments the received message with headers providing a measure of its spamness.
// Delete instructs Mailgun to just block or delete the message all-together.
const (
SpamActionTag = SpamAction("tag")
// Tag the received message with headers providing a measure of its spamness.
SpamActionTag = SpamAction("tag")
// Prevents Mailgun from taking any action on what it perceives to be spam.
SpamActionDisabled = SpamAction("disabled")
SpamActionDelete = SpamAction("delete")
// instructs Mailgun to just block or delete the message all-together.
SpamActionDelete = SpamAction("delete")
)

type SpamAction string
Expand All @@ -31,9 +25,8 @@ type Domain struct {
Name string `json:"name"`
SMTPPassword string `json:"smtp_password"`
Wildcard bool `json:"wildcard"`
// The SpamAction field must be one of Tag, Disabled, or Delete.
SpamAction string `json:"spam_action"`
State string `json:"state"`
SpamAction SpamAction `json:"spam_action"`
State string `json:"state"`
}

// DNSRecord structures describe intended records to properly configure your domain for use with Mailgun.
Expand All @@ -56,22 +49,26 @@ type domainConnectionResponse struct {
Connection DomainConnection `json:"connection"`
}

type domainListResponse struct {
type domainsListResponse struct {
// is -1 if Next() or First() have not been called
TotalCount int `json:"total_count"`
Items []Domain `json:"items"`
}

// Specify the domain connection options
type DomainConnection struct {
RequireTLS bool `json:"require_tls"`
SkipVerification bool `json:"skip_verification"`
}

// Specify the domain tracking options
type DomainTracking struct {
Click TrackingStatus `json:"click"`
Open TrackingStatus `json:"open"`
Unsubscribe TrackingStatus `json:"unsubscribe"`
}

// The tracking status of a domain
type TrackingStatus struct {
Active bool `json:"active"`
HTMLFooter string `json:"html_footer"`
Expand All @@ -83,30 +80,151 @@ type domainTrackingResponse struct {
}

// ListDomains retrieves a set of domains from Mailgun.
//
// Assuming no error, both the number of items retrieved and a slice of Domain instances.
// The number of items returned may be less than the specified limit, if it's specified.
// Note that zero items and a zero-length slice do not necessarily imply an error occurred.
// Except for the error itself, all results are undefined in the event of an error.
func (mg *MailgunImpl) ListDomains(ctx context.Context, opts *ListOptions) (int, []Domain, error) {
r := newHTTPRequest(generatePublicApiUrl(mg, domainsEndpoint))
r.setClient(mg.Client())
r.setBasicAuth(basicAuthUser, mg.APIKey())
func (mg *MailgunImpl) ListDomains(ctx context.Context, opts *ListOptions) *DomainsIterator {
var limit int
if opts != nil {
limit = opts.Limit
}
return &DomainsIterator{
mg: mg,
url: generatePublicApiUrl(mg, domainsEndpoint),
domainsListResponse: domainsListResponse{TotalCount: -1},
limit: limit,
}
}

type DomainsIterator struct {
domainsListResponse

limit int
mg Mailgun
offset int
url string
err error
}

// If an error occurred during iteration `Err()` will return non nil
func (ri *DomainsIterator) Err() error {
return ri.err
}

// Returns the current offset of the iterator
func (ri *DomainsIterator) Offset() int {
return ri.offset
}

// Retrieves the next page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error
func (ri *DomainsIterator) Next(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}

ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}

cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
ri.offset = len(ri.Items)
return true
}

// Retrieves the first page of items from the api. Returns false if there
// was an error. It also sets the iterator object to the first page.
// Use `.Err()` to retrieve the error.
func (ri *DomainsIterator) First(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}
ri.err = ri.fetch(ctx, 0, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
ri.offset = len(ri.Items)
return true
}

// Retrieves the last page of items from the api.
// Calling Last() is invalid unless you first call First() or Next()
// Returns false if there was an error. It also sets the iterator object
// to the last page. Use `.Err()` to retrieve the error.
func (ri *DomainsIterator) Last(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}

if ri.TotalCount == -1 {
return false
}

if opts != nil && opts.Limit != 0 {
r.addParameter("limit", strconv.Itoa(opts.Limit))
ri.offset = ri.TotalCount - ri.limit
if ri.offset < 0 {
ri.offset = 0
}

if opts != nil && opts.Skip != 0 {
r.addParameter("skip", strconv.Itoa(opts.Skip))
ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
return true
}

// Retrieves the previous page of items from the api. Returns false when there
// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve
// the error if any
func (ri *DomainsIterator) Previous(ctx context.Context, items *[]Domain) bool {
if ri.err != nil {
return false
}

var list domainListResponse
err := getResponseFromJSON(ctx, r, &list)
if err != nil {
return -1, nil, err
if ri.TotalCount == -1 {
return false
}
return list.TotalCount, list.Items, nil

ri.offset = ri.offset - ri.limit
if ri.offset < 0 {
ri.offset = 0
}

ri.err = ri.fetch(ctx, ri.offset, ri.limit)
if ri.err != nil {
return false
}
cpy := make([]Domain, len(ri.Items))
copy(cpy, ri.Items)
*items = cpy
if len(ri.Items) == 0 {
return false
}
return true
}

func (ri *DomainsIterator) fetch(ctx context.Context, skip, limit int) error {
r := newHTTPRequest(ri.url)
r.setBasicAuth(basicAuthUser, ri.mg.APIKey())
r.setClient(ri.mg.Client())

if skip != 0 {
r.addParameter("skip", strconv.Itoa(skip))
}
if limit != 0 {
r.addParameter("limit", strconv.Itoa(limit))
}

return getResponseFromJSON(ctx, r, &ri.domainsListResponse)
}

// Retrieve detailed information about the named domain.
Expand All @@ -119,6 +237,7 @@ func (mg *MailgunImpl) GetDomain(ctx context.Context, domain string) (Domain, []
return resp.Domain, resp.ReceivingDNSRecords, resp.SendingDNSRecords, err
}

// Optional parameters when creating a domain
type CreateDomainOptions struct {
SpamAction SpamAction
Wildcard bool
Expand Down
26 changes: 15 additions & 11 deletions domains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,34 @@ const (
testKey = "api-fake-key"
)

func TestGetDomains(t *testing.T) {
func TestListDomains(t *testing.T) {
mg := mailgun.NewMailgun(testDomain, testKey)
mg.SetAPIBase(server.URL())
ctx := context.Background()

n, domains, err := mg.ListDomains(ctx, nil)
ensure.Nil(t, err)
ensure.DeepEqual(t, len(domains) != 0, true)

t.Logf("TestGetDomains: %d domains retrieved\n", n)
for _, d := range domains {
t.Logf("TestGetDomains: %#v\n", d)
it := mg.ListDomains(ctx, nil)
var page []mailgun.Domain
for it.Next(ctx, &page) {
for _, d := range page {
t.Logf("TestListDomains: %#v\n", d)
}
}
t.Logf("TestListDomains: %d domains retrieved\n", it.TotalCount)
ensure.Nil(t, it.Err())
ensure.True(t, it.TotalCount != 0)
}

func TestGetSingleDomain(t *testing.T) {
mg := mailgun.NewMailgun(testDomain, testKey)
mg.SetAPIBase(server.URL())
ctx := context.Background()

_, domains, err := mg.ListDomains(ctx, nil)
ensure.Nil(t, err)
it := mg.ListDomains(ctx, nil)
var page []mailgun.Domain
ensure.True(t, it.Next(ctx, &page))
ensure.Nil(t, it.Err())

dr, rxDnsRecords, txDnsRecords, err := mg.GetDomain(ctx, domains[0].Name)
dr, rxDnsRecords, txDnsRecords, err := mg.GetDomain(ctx, page[0].Name)
ensure.Nil(t, err)
ensure.DeepEqual(t, len(rxDnsRecords) != 0, true)
ensure.DeepEqual(t, len(txDnsRecords) != 0, true)
Expand Down
Loading

0 comments on commit 9651329

Please sign in to comment.