Skip to content

Commit

Permalink
add docs and linter (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrwormhole authored Jun 15, 2024
1 parent 05da947 commit 1c73e84
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 9 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ jobs:
go-version: '1.22.x'

- name: Lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: v1.57
version: v1.59.1

- name: Test
run: go test -race -covermode atomic -coverprofile=covprofile -v ./...
Expand Down
79 changes: 79 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# If this config is confusing, have a look here, it is documented from A-Z https://golangci-lint.run/usage/linters
linters-settings:
revive:
max-open-files: 2048 # Maximum number of open files at the same time.
ignore-generated-header: false # When set to false, ignores files with "GENERATED" header, similar to golint.
severity: warning # Sets the default severity.
enable-all-rules: false # Enable all available rules.
confidence: 0.8 # This means that linting errors with less than 0.8 confidence will be ignored.
rules:
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#exported
- name: exported
severity: warning
disabled: false
arguments:
- "checkPrivateReceivers"
- "sayRepetitiveInsteadOfStutters"
interfacebloat:
max: 5 # The maximum number of methods allowed for an interface.
goconst:
min-len: 3 # Minimal length of string constant.
min-occurrences: 3 # Minimum occurrences of constant string count to trigger issue.
ignore-tests: true
gci:
custom-order: true
sections:
- standard # Standard section: captures all standard packages.
- default # Default section: contains all imports that could not be matched to another section type.
- prefix(github.com/mrwormhole/emailer) # Custom section: groups all imports with the specified Prefix.
skip-generated: false
nolintlint:
allow-unused: false # report any unused nolint directives
require-explanation: false # don't require an explanation for nolint directives
require-specific: true # require nolint directives to be specific about which linter is being skipped

linters:
disable-all: true
enable:
# common mistakes
- govet
- staticcheck
- gosec
- bodyclose
- ineffassign
- errcheck
- typecheck
- durationcheck
- nilerr
- nilnil
- nolintlint
- wrapcheck
- sloglint
- noctx
- interfacebloat

# common styling
- gofmt
- goimports
- gci
- goconst
- gosimple
- dogsled
- errname
- forcetypeassert
- predeclared
- tenv
- unconvert
- unparam
- unused
- usestdlibvars
- whitespace
- stylecheck
- revive

issues:
exclude-use-default: false

run:
tests: true
timeout: 30s
23 changes: 23 additions & 0 deletions brevo/client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
// Package brevo makes it easy to send emails via brevo provider. This package follows [brevo spec] strictly.
//
// Example usage:
//
// email := emailer.Email{
// From: "[email protected]",
// To: []string{"[email protected]"},
// Subject: "peace",
// TextContent: "peace was never an option",
// }
// c, err := New("api-key", &http.Client{})
// if err != nil {
// //check err
// }
// c.Send(ctx, email)
//
// [brevo spec]: https://developers.brevo.com/reference/sendtransacemail
package brevo

import (
Expand All @@ -16,11 +33,13 @@ import (

const endpoint = "https://api.brevo.com/v3/smtp/email"

// EmailClient is brevo email client to interact with emails
type EmailClient struct {
key string
client *http.Client
}

// New creates a new brevo email client with given API key and http.Client
func New(key string, client *http.Client) (*EmailClient, error) {
if strings.TrimSpace(key) == "" {
return nil, errors.New("brevo API key is blank")
Expand All @@ -32,10 +51,12 @@ func New(key string, client *http.Client) (*EmailClient, error) {
}, nil
}

// Detail is additional info about the person such as email and name
type Detail struct {
Email string `json:"email"`
}

// Payload is a request that brevo uses to send email
type Payload struct {
Sender Detail `json:"sender"`
To []Detail `json:"to"`
Expand All @@ -46,11 +67,13 @@ type Payload struct {
TextContent string `json:"textContent,omitempty"`
}

// CodeMessage is a response when brevo encounters a problem while sending email
type CodeMessage struct {
Code string `json:"code"`
Message string `json:"message"`
}

// Send sends a given email
func (c *EmailClient) Send(ctx context.Context, email emailer.Email) error {
var p Payload
p.Sender.Email = email.From
Expand Down
1 change: 1 addition & 0 deletions brevo/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/mrwormhole/emailer"
)

// EmailHandler is opinionated/reusable HTTP handler for brevo provider
func EmailHandler(sender emailer.Sender) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var e emailer.Email
Expand Down
2 changes: 1 addition & 1 deletion brevo/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestEmailHandler_FailedValidation(t *testing.T) {
func TestEmailHandler_Success(t *testing.T) {
tripper := func(req *http.Request) *http.Response {
return &http.Response{
StatusCode: 200,
StatusCode: http.StatusOK,
}
}
client, err := NewTestClient[RoundTripFunc](tripper)
Expand Down
15 changes: 9 additions & 6 deletions cmd/emailer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ func main() {
provider = providerBrevo
}

c := retryablehttp.NewClient()
c.RetryMax = 3
httpClient := c.StandardClient()
httpClient.Timeout = 10 * time.Second

var handler http.HandlerFunc
switch {
case strings.EqualFold(provider, providerBrevo):
c := retryablehttp.NewClient()
c.RetryMax = 3
httpClient := c.StandardClient()
httpClient.Timeout = 10 * time.Second
sender, err := brevo.New(key, httpClient)
if err != nil {
slog.LogAttrs(context.Background(), slog.LevelError, "brevo.New()", slog.String("err", err.Error()))
Expand All @@ -79,8 +80,10 @@ func main() {
mux.HandleFunc("POST /email", handler)

srv := &http.Server{
Addr: fmt.Sprintf("localhost:%d", portNum),
Handler: mux,
Addr: fmt.Sprintf("localhost:%d", portNum),
Handler: mux,
ReadTimeout: 5 * time.Second,
WriteTimeout: 5 * time.Second,
}

go func() {
Expand Down
4 changes: 4 additions & 0 deletions email.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package emailer provides shared foundation stones for email providers
package emailer

import (
Expand All @@ -9,6 +10,7 @@ import (

var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

// Email is generic email structure for all providers
type Email struct {
From string `json:"from"`
To []string `json:"to"`
Expand All @@ -19,6 +21,7 @@ type Email struct {
TextContent string `json:"textContent"`
}

// ValidationMsg returns empty if all validations passed, else it will return failed validation message
func (e Email) ValidationMsg() string {
if strings.TrimSpace(e.From) == "" {
return "from field must not be blank"
Expand Down Expand Up @@ -53,6 +56,7 @@ func (e Email) ValidationMsg() string {
return ""
}

// Sender is a behaviour for email senders
type Sender interface {
Send(ctx context.Context, e Email) error
}

0 comments on commit 1c73e84

Please sign in to comment.