Repository: mailgun/mailgun-go
Pull request #383 (2025-02-01 15:20:49)

DE-1412 Move ValidateEmail() to MailgunImpl


Patch:
diff --git a/email_validation.go b/email_validation.go
deleted file mode 100644
index 3589f81..0000000
--- a/email_validation.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package mailgun
-
-import (
-	"context"
-	"fmt"
-	"net/http"
-	"os"
-
-	"github.com/mailgun/errors"
-)
-
-// EmailVerification records basic facts about a validated e-mail address.
-// See the ValidateEmail method and example for more details.
-type EmailVerification struct {
-	// Echoes the address provided.
-	Address string `json:"address"`
-
-	// Indicates whether Mailgun thinks the address is from a known
-	// disposable mailbox provider.
-	IsDisposableAddress bool `json:"is_disposable_address"`
-
-	// Indicates whether Mailgun thinks the address is an email distribution list.
-	IsRoleAddress bool `json:"is_role_address"`
-
-	// A list of potential reasons why a specific validation may be unsuccessful. (Available in the v4 response)
-	Reason []string `json:"reason"`
-
-	// Result
-	Result string `json:"result"`
-
-	// Risk assessment for the provided email.
-	Risk string `json:"risk"`
-
-	LastSeen int64 `json:"last_seen,omitempty"`
-
-	// Provides a simple recommendation in case the address is invalid or
-	// Mailgun thinks you might have a typo. May be empty, in which case
-	// Mailgun has no recommendation to give.
-	DidYouMean string `json:"did_you_mean,omitempty"`
-
-	// Engagement results are a macro-level view that explain an email recipient’s propensity to engage.
-	// https://documentation.mailgun.com/docs/inboxready/mailgun-validate/validate_engagement/
-	Engagement *EngagementData `json:"engagement,omitempty"`
-
-	RootAddress string `json:"root_address,omitempty"`
-}
-
-type EngagementData struct {
-	Engaging bool   `json:"engaging"`
-	IsBot    bool   `json:"is_bot"`
-	Behavior string `json:"behavior,omitempty"`
-}
-
-type EmailValidator interface {
-	ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error)
-}
-
-// TODO(v5): switch to MailgunImpl
-type EmailValidatorImpl struct {
-	client  *http.Client
-	apiBase string
-	apiKey  string
-}
-
-// NewEmailValidator creates a new validation instance.
-// Use private key.
-func NewEmailValidator(apiKey string) *EmailValidatorImpl {
-	return &EmailValidatorImpl{
-		// TODO(vtopc): Don’t use http.DefaultClient - https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779
-		client:  http.DefaultClient,
-		apiBase: "https://api.mailgun.net/v4",
-		apiKey:  apiKey,
-	}
-}
-
-// NewEmailValidatorFromEnv returns a new EmailValidator using environment variables
-//
-// Set MG_API_KEY
-func NewEmailValidatorFromEnv() (*EmailValidatorImpl, error) {
-	apiKey := os.Getenv("MG_API_KEY")
-	if apiKey == "" {
-		return nil, errors.New(
-			"environment variable MG_API_KEY required for email validation")
-	}
-
-	v := NewEmailValidator(apiKey)
-	url := os.Getenv("MG_URL")
-	if url != "" {
-		v.SetAPIBase(url)
-	}
-	return v, nil
-}
-
-// APIBase returns the API Base URL configured for this client.
-func (m *EmailValidatorImpl) APIBase() string {
-	return m.apiBase
-}
-
-// SetAPIBase updates the API Base URL for this client.
-func (m *EmailValidatorImpl) SetAPIBase(address string) {
-	m.apiBase = address
-}
-
-// SetClient updates the HTTP client for this client.
-func (m *EmailValidatorImpl) SetClient(c *http.Client) {
-	m.client = c
-}
-
-// Client returns the HTTP client configured for this client.
-func (m *EmailValidatorImpl) Client() *http.Client {
-	return m.client
-}
-
-// APIKey returns the API key used for validations
-func (m *EmailValidatorImpl) APIKey() string {
-	return m.apiKey
-}
-
-// ValidateEmail performs various checks on the email address provided to ensure it's correctly formatted.
-// It may also be used to break an email address into its sub-components. If user has set the
-// TODO(v5): move to *MailgunImpl?
-func (m *EmailValidatorImpl) ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (EmailVerification, error) {
-	r := newHTTPRequest(fmt.Sprintf("%s/v4/address/validate", m.APIBase()))
-	r.setClient(m.Client())
-	r.addParameter("address", email)
-	if mailBoxVerify {
-		r.addParameter("mailbox_verification", "true")
-	}
-	r.setBasicAuth(basicAuthUser, m.APIKey())
-
-	var res EmailVerification
-	err := getResponseFromJSON(ctx, r, &res)
-	if err != nil {
-		return EmailVerification{}, err
-	}
-	return res, nil
-}
diff --git a/examples/examples.go b/examples/examples.go
index da2b4b5..f04ab4c 100644
--- a/examples/examples.go
+++ b/examples/examples.go
@@ -608,13 +608,13 @@ func ListUnsubscribes(domain, apiKey string) ([]mailgun.Unsubscribe, error) {
 	return result, nil
 }
 
-func ValidateEmail(apiKey string) (mailgun.EmailVerification, error) {
-	mv := mailgun.NewEmailValidator(apiKey)
+func ValidateEmail(apiKey string) (mailgun.ValidateEmailResponse, error) {
+	mg := mailgun.NewMailgun(apiKey)
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
 	defer cancel()
 
-	return mv.ValidateEmail(ctx, "foo@mailgun.net", false)
+	return mg.ValidateEmail(ctx, "foo@mailgun.net", false)
 }
 
 func GetWebhook(domain, apiKey string) ([]string, error) {
diff --git a/mailgun.go b/mailgun.go
index 03d78c5..c62a438 100644
--- a/mailgun.go
+++ b/mailgun.go
@@ -234,6 +234,8 @@ type Mailgun interface {
 	DeleteTemplateVersion(ctx context.Context, domain, templateName, tag string) error
 	ListTemplateVersions(domain, templateName string, opts *ListOptions) *TemplateVersionsIterator
 
+	ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (ValidateEmailResponse, error)
+
 	ListSubaccounts(opts *ListSubaccountsOptions) *SubaccountsIterator
 	CreateSubaccount(ctx context.Context, subaccountName string) (SubaccountResponse, error)
 	SubaccountDetails(ctx context.Context, subaccountId string) (SubaccountResponse, error)
diff --git a/mock_validation.go b/mock_validation.go
index 29291a5..8d4a918 100644
--- a/mock_validation.go
+++ b/mock_validation.go
@@ -17,7 +17,7 @@ func (ms *mockServer) validateEmailV4(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	var results EmailVerification
+	var results ValidateEmailResponse
 	results.Address = r.FormValue("address")
 	results.Reason = []string{"no-reason"}
 	results.Risk = "unknown"
diff --git a/validation.go b/validation.go
new file mode 100644
index 0000000..1fedc36
--- /dev/null
+++ b/validation.go
@@ -0,0 +1,69 @@
+package mailgun
+
+import (
+	"context"
+	"fmt"
+)
+
+// ValidateEmailResponse records basic facts about a validated e-mail address.
+// See the ValidateEmail method and example for more details.
+type ValidateEmailResponse struct {
+	// Echoes the address provided.
+	Address string `json:"address"`
+
+	// Indicates whether Mailgun thinks the address is from a known
+	// disposable mailbox provider.
+	IsDisposableAddress bool `json:"is_disposable_address"`
+
+	// Indicates whether Mailgun thinks the address is an email distribution list.
+	IsRoleAddress bool `json:"is_role_address"`
+
+	// A list of potential reasons why a specific validation may be unsuccessful. (Available in the v4 response)
+	Reason []string `json:"reason"`
+
+	// Result
+	Result string `json:"result"`
+
+	// Risk assessment for the provided email.
+	Risk string `json:"risk"`
+
+	LastSeen int64 `json:"last_seen,omitempty"`
+
+	// Provides a simple recommendation in case the address is invalid or
+	// Mailgun thinks you might have a typo. May be empty, in which case
+	// Mailgun has no recommendation to give.
+	DidYouMean string `json:"did_you_mean,omitempty"`
+
+	// Engagement results are a macro-level view that explain an email recipient’s propensity to engage.
+	// https://documentation.mailgun.com/docs/inboxready/mailgun-validate/validate_engagement/
+	Engagement *EngagementData `json:"engagement,omitempty"`
+
+	RootAddress string `json:"root_address,omitempty"`
+}
+
+type EngagementData struct {
+	Engaging bool   `json:"engaging"`
+	IsBot    bool   `json:"is_bot"`
+	Behavior string `json:"behavior,omitempty"`
+}
+
+// ValidateEmail performs various checks on the email address provided to ensure it's correctly formatted.
+// It may also be used to break an email address into its sub-components.
+// https://documentation.mailgun.com/docs/inboxready/mailgun-validate/single-valid-ir/
+func (mg *MailgunImpl) ValidateEmail(ctx context.Context, email string, mailBoxVerify bool) (ValidateEmailResponse, error) {
+	r := newHTTPRequest(fmt.Sprintf("%s/v4/address/validate", mg.APIBase()))
+	r.setClient(mg.HTTPClient())
+	r.addParameter("address", email)
+	if mailBoxVerify {
+		r.addParameter("mailbox_verification", "true")
+	}
+	r.setBasicAuth(basicAuthUser, mg.APIKey())
+
+	var res ValidateEmailResponse
+	err := getResponseFromJSON(ctx, r, &res)
+	if err != nil {
+		return ValidateEmailResponse{}, err
+	}
+
+	return res, nil
+}


Test patch:
diff --git a/examples_test.go b/examples_test.go
index 261a214..1e296ab 100644
--- a/examples_test.go
+++ b/examples_test.go
@@ -15,13 +15,13 @@ import (
 	"github.com/mailgun/mailgun-go/v4/events"
 )
 
-func ExampleEmailValidatorImpl_ValidateEmail() {
-	v := mailgun.NewEmailValidator("my_api_key")
+func ExampleMailgunImpl_ValidateEmail() {
+	mg := mailgun.NewMailgun("my_api_key")
 
 	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
 	defer cancel()
 
-	ev, err := v.ValidateEmail(ctx, "joe@example.com", false)
+	ev, err := mg.ValidateEmail(ctx, "joe@example.com", false)
 	if err != nil {
 		log.Fatal(err)
 	}
diff --git a/email_validation_test.go b/validation_test.go
similarity index 75%
rename from email_validation_test.go
rename to validation_test.go
index 2d9edca..2409cb3 100644
--- a/email_validation_test.go
+++ b/validation_test.go
@@ -9,13 +9,14 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-func TestEmailValidationV4(t *testing.T) {
-	v := mailgun.NewEmailValidator(testKey)
-	v.SetAPIBase(server.URL())
+func TestValidateEmail(t *testing.T) {
+	mg := mailgun.NewMailgun(testKey)
+	err := mg.SetAPIBase(server.URL())
+	require.NoError(t, err)
 
 	ctx := context.Background()
 
-	ev, err := v.ValidateEmail(ctx, "foo@mailgun.com", false)
+	ev, err := mg.ValidateEmail(ctx, "foo@mailgun.com", false)
 	require.NoError(t, err)
 
 	assert.False(t, ev.IsDisposableAddress)