Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ jobs:
with:
go-version: stable
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
uses: golangci/golangci-lint-action@v7
with:
version: v1.60
version: v2.11.3
103 changes: 42 additions & 61 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,69 +1,50 @@
# options for analysis running
run:
# timeout for analysis, e.g. 30s, 5m, default is 1m
deadline: 2m

issues:
# Only report issues for changes since master
new-from-rev: origin/master

# output configuration options
output:
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
format: colored-line-number

linters-settings:
errcheck:
# report about not checking of errors in type assertions: `a := b.(MyStruct)`;
# default is false: such cases aren't reported by default.
check-type-assertions: true

# Function length check
funlen:
lines: 60
statements: 40

# Report deeply nested if statements
nestif:
# minimal complexity of if statements to report, 5 by default
min-complexity: 4

gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
version: "2"

golint:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8

govet:
# report about shadowed variables
check-shadowing: true
enable-all: true
disable:
# Do not check field memory alignment because in most cases the performance gain is not worth the headache
- fieldalignment
run:
timeout: 2m

linters:
# Disable the default linters so we can explicitly name the linters we want
disable-all: true

# List of enabled linters
default: none
enable:
#####################
# Default linters
#####################
- gofmt
# Checks error handling
- errcheck
# Linter for Go source code that specializes in simplifying a code
- gosimple
# Vet examines Go source code and reports suspicious constructs, such as Printf calls whose
# arguments do not align with the format string
- govet
# Detects when assignments to existing variables are not used
- ineffassign
# Static code analytics
- staticcheck
# Unused checks
- unused
- unused
settings:
errcheck:
check-type-assertions: true
funlen:
lines: 60
statements: 40
govet:
disable:
- fieldalignment
enable-all: true
nestif:
min-complexity: 4
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
issues:
new-from-rev: origin/master
formatters:
enable:
- gofmt
settings:
gofmt:
simplify: true
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
11 changes: 8 additions & 3 deletions internal/examples/memory/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
Expand All @@ -18,12 +19,16 @@ type myMessage struct {

func (e *myMessage) UnmarshalJSON(data []byte) error {
var raw map[string]any
err := json.Unmarshal(data, &raw)
if err != nil {
if err := json.Unmarshal(data, &raw); err != nil {
return err
}

e.name = raw["name"].(string)
name, ok := raw["name"].(string)
if !ok {
return fmt.Errorf("missing or invalid field: name")
}

e.name = name

return nil
}
Expand Down
42 changes: 42 additions & 0 deletions internal/examples/memory/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestMyMessage_UnmarshalJSON_Success(t *testing.T) {
var m myMessage
err := json.Unmarshal([]byte(`{"name":"hello"}`), &m)
assert.NoError(t, err)
assert.Equal(t, "hello", m.name)
}

func TestMyMessage_UnmarshalJSON_MissingField(t *testing.T) {
var m myMessage
err := json.Unmarshal([]byte(`{}`), &m)
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing or invalid field: name")
}

func TestMyMessage_UnmarshalJSON_WrongType(t *testing.T) {
var m myMessage
err := json.Unmarshal([]byte(`{"name": 42}`), &m)
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing or invalid field: name")
}

func TestMyMessage_UnmarshalJSON_InvalidJSON(t *testing.T) {
var m myMessage
err := json.Unmarshal([]byte(`not-json`), &m)
assert.Error(t, err)
}

func TestMyMessage_MarshalJSON(t *testing.T) {
m := myMessage{name: "hello"}
data, err := json.Marshal(&m)
assert.NoError(t, err)
assert.JSONEq(t, `{"name":"hello"}`, string(data))
}
11 changes: 8 additions & 3 deletions internal/examples/sqs/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sqs

import (
"encoding/json"
"fmt"
)

type MyEvent struct {
Expand All @@ -10,12 +11,16 @@ type MyEvent struct {

func (e *MyEvent) UnmarshalJSON(data []byte) error {
var raw map[string]any
err := json.Unmarshal(data, &raw)
if err != nil {
if err := json.Unmarshal(data, &raw); err != nil {
return err
}

e.Name = raw["name"].(string)
name, ok := raw["name"].(string)
if !ok {
return fmt.Errorf("missing or invalid field: name")
}

e.Name = name

return nil
}
Expand Down
42 changes: 42 additions & 0 deletions internal/examples/sqs/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package sqs

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestMyEvent_UnmarshalJSON_Success(t *testing.T) {
var e MyEvent
err := json.Unmarshal([]byte(`{"name":"test-event"}`), &e)
assert.NoError(t, err)
assert.Equal(t, "test-event", e.Name)
}

func TestMyEvent_UnmarshalJSON_MissingField(t *testing.T) {
var e MyEvent
err := json.Unmarshal([]byte(`{}`), &e)
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing or invalid field: name")
}

func TestMyEvent_UnmarshalJSON_WrongType(t *testing.T) {
var e MyEvent
err := json.Unmarshal([]byte(`{"name": 42}`), &e)
assert.Error(t, err)
assert.Contains(t, err.Error(), "missing or invalid field: name")
}

func TestMyEvent_UnmarshalJSON_InvalidJSON(t *testing.T) {
var e MyEvent
err := json.Unmarshal([]byte(`not-json`), &e)
assert.Error(t, err)
}

func TestMyEvent_MarshalJSON(t *testing.T) {
e := MyEvent{Name: "test-event"}
data, err := json.Marshal(&e)
assert.NoError(t, err)
assert.JSONEq(t, `{"name":"test-event"}`, string(data))
}
26 changes: 11 additions & 15 deletions sqs/sqs.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package sqs

import (
"errors"
"fmt"
"net/url"
"os"
Expand Down Expand Up @@ -37,12 +36,7 @@ func New(options ...Option) (*Driver, error) {
}

if driver.sqsClient == nil {
clientCredentials, err := getCredentials()
if err != nil {
return nil, err
}

client, err := createClient(driver.url, driver.region, clientCredentials)
client, err := createClient(driver.url, driver.region, getCredentials())
if err != nil {
return nil, err
}
Expand All @@ -59,16 +53,18 @@ func New(options ...Option) (*Driver, error) {
return driver, nil
}

func getCredentials() (*credentials.Credentials, error) {
// getCredentials returns explicit credentials when the legacy env vars are set,
// or nil to fall through to the AWS SDK default credential chain (which supports
// ECS/Pod Identity, instance profiles, env vars, and shared credentials files).
func getCredentials() *credentials.Credentials {
if os.Getenv("AWS_SHARED_CREDENTIALS_FILE") != "" {
return credentials.NewSharedCredentials("", ""), nil
} else if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" {
return credentials.NewEnvCredentials(), nil
return credentials.NewSharedCredentials("", "")
}

return nil, errors.New(
"missing AWS_SHARED_CREDENTIALS_FILE and AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY env vars",
)
if os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" {
return credentials.NewEnvCredentials()
}
// nil tells the SDK to use its built-in default chain, including Pod Identity.
return nil
}

func createClient(queueUrl string, region string, clientCredentials *credentials.Credentials) (*sqs.SQS, error) {
Expand Down
29 changes: 28 additions & 1 deletion sqs/sqs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,34 @@ func (suite *SQSTestSuite) TestNewWithDefaultOptions() {
_, err := sqs.New()

suite.Error(err)
suite.Contains(err.Error(), "missing")
suite.Contains(err.Error(), "error creating sqs client")
}

// TestNewWithEnvCredentials verifies that static key/secret env vars are accepted.
func (suite *SQSTestSuite) TestNewWithEnvCredentials() {
os.Setenv("AWS_ACCESS_KEY_ID", "AKIAIOSFODNN7EXAMPLE")
os.Setenv("AWS_SECRET_ACCESS_KEY", "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY")

_, err := sqs.New(
sqs.WithUrl("https://sqs.eu-central-1.amazonaws.com"),
sqs.WithRegion("us-east-1"),
)

suite.Nil(err)
}

// TestNewWithNoCredentialEnvVars verifies that the driver can be created when no
// explicit credential env vars are set, falling through to the AWS SDK default
// credential chain (which covers Pod Identity, instance profiles, etc.).
func (suite *SQSTestSuite) TestNewWithNoCredentialEnvVars() {
_, err := sqs.New(
sqs.WithUrl("https://sqs.eu-central-1.amazonaws.com"),
sqs.WithRegion("us-east-1"),
)

// The driver should be created without error — credential resolution is
// deferred to the first actual API call, not at construction time.
suite.Nil(err)
}

func (suite *SQSTestSuite) TestNew_InvalidQueueURL() {
Expand Down
Loading