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
46 changes: 31 additions & 15 deletions .coverage
Original file line number Diff line number Diff line change
Expand Up @@ -269,27 +269,43 @@ github.com/adjoeio/djoemo/metrics.go:37.73,39.9 2 1
github.com/adjoeio/djoemo/metrics.go:39.9,44.3 2 1
github.com/adjoeio/djoemo/metrics.go:46.2,50.12 4 1
github.com/adjoeio/djoemo/metrics.go:54.73,56.2 1 1
github.com/adjoeio/djoemo/metrics.go:58.63,60.32 2 1
github.com/adjoeio/djoemo/metrics.go:60.32,62.3 1 0
github.com/adjoeio/djoemo/metrics.go:58.66,60.32 2 1
github.com/adjoeio/djoemo/metrics.go:60.32,62.3 1 1
github.com/adjoeio/djoemo/metrics.go:63.2,63.28 1 1
github.com/adjoeio/djoemo/metrics.go:66.21,68.2 1 0
github.com/adjoeio/djoemo/metrics.go:66.21,68.2 1 1
github.com/adjoeio/djoemo/metrics.go:74.48,76.2 1 1
github.com/adjoeio/djoemo/metrics.go:78.118,79.35 1 1
github.com/adjoeio/djoemo/metrics.go:78.114,79.35 1 1
github.com/adjoeio/djoemo/metrics.go:79.35,81.3 1 1
github.com/adjoeio/djoemo/metrics.go:84.128,85.26 1 0
github.com/adjoeio/djoemo/metrics.go:84.124,85.26 1 0
github.com/adjoeio/djoemo/metrics.go:85.26,87.3 1 0
github.com/adjoeio/djoemo/metrics_cloudwatch.go:10.126,13.2 0 0
github.com/adjoeio/djoemo/metrics_cloudwatch.go:16.111,17.2 0 0
github.com/adjoeio/djoemo/metrics_prometheus.go:19.78,27.2 4 0
github.com/adjoeio/djoemo/metrics_prometheus.go:29.85,40.2 4 0
github.com/adjoeio/djoemo/metrics_prometheus.go:49.77,56.2 2 0
github.com/adjoeio/djoemo/metrics_prometheus.go:58.128,59.67 1 0
github.com/adjoeio/djoemo/metrics_prometheus.go:59.67,62.3 2 0
github.com/adjoeio/djoemo/metrics_prometheus.go:64.2,65.13 2 0
github.com/adjoeio/djoemo/metrics_prometheus.go:65.13,67.3 1 0
github.com/adjoeio/djoemo/metrics_prometheus.go:69.2,70.27 2 0
github.com/adjoeio/djoemo/metrics_prometheus.go:70.27,72.3 1 0
github.com/adjoeio/djoemo/metrics_prometheus.go:74.2,77.65 3 0
github.com/adjoeio/djoemo/metrics_prometheus.go:24.78,30.53 3 1
github.com/adjoeio/djoemo/metrics_prometheus.go:30.53,31.61 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:31.61,33.4 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:34.3,34.13 1 0
github.com/adjoeio/djoemo/metrics_prometheus.go:36.2,36.16 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:39.85,47.55 3 1
github.com/adjoeio/djoemo/metrics_prometheus.go:47.55,48.61 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:48.61,50.4 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:51.3,51.13 1 0
github.com/adjoeio/djoemo/metrics_prometheus.go:53.2,53.18 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:63.77,70.2 2 1
github.com/adjoeio/djoemo/metrics_prometheus.go:72.128,78.32 5 1
github.com/adjoeio/djoemo/metrics_prometheus.go:78.32,80.34 2 1
github.com/adjoeio/djoemo/metrics_prometheus.go:80.34,82.4 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:83.3,83.37 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:83.37,85.4 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:86.3,88.16 3 1
github.com/adjoeio/djoemo/metrics_prometheus.go:91.2,92.13 2 1
github.com/adjoeio/djoemo/metrics_prometheus.go:92.13,94.3 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:96.2,97.41 2 1
github.com/adjoeio/djoemo/metrics_prometheus.go:97.41,99.3 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:101.2,106.31 3 1
github.com/adjoeio/djoemo/metrics_prometheus.go:106.31,108.46 1 1
github.com/adjoeio/djoemo/metrics_prometheus.go:108.46,112.4 2 1
github.com/adjoeio/djoemo/metrics_prometheus.go:112.9,114.4 1 0
github.com/adjoeio/djoemo/metrics_prometheus.go:117.2,118.52 2 1
github.com/adjoeio/djoemo/model.go:11.35,13.2 1 1
github.com/adjoeio/djoemo/model.go:16.35,18.2 1 1
github.com/adjoeio/djoemo/model.go:21.33,22.24 1 1
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
vendor
Gopkg.*
.coverage
362 changes: 173 additions & 189 deletions README.md

Large diffs are not rendered by default.

119 changes: 2 additions & 117 deletions djoemo_suite_test.go
Original file line number Diff line number Diff line change
@@ -1,127 +1,18 @@
package djoemo_test

import (
"context"
"testing"
"time"

"github.com/onsi/ginkgo/v2"
"github.com/onsi/gomega"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestDjoemo(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Djoemo Suite")
}

// Declarations for Ginkgo DSL
type Done ginkgo.Done
type Benchmarker ginkgo.Benchmarker

var GinkgoWriter = ginkgo.GinkgoWriter
var GinkgoRandomSeed = ginkgo.GinkgoRandomSeed
var GinkgoParallelNode = ginkgo.GinkgoParallelNode
var GinkgoT = ginkgo.GinkgoT
var CurrentGinkgoTestDescription = ginkgo.CurrentGinkgoTestDescription
var RunSpecs = ginkgo.RunSpecs
var RunSpecsWithDefaultAndCustomReporters = ginkgo.RunSpecsWithDefaultAndCustomReporters
var RunSpecsWithCustomReporters = ginkgo.RunSpecsWithCustomReporters
var Skip = ginkgo.Skip
var Fail = ginkgo.Fail
var GinkgoRecover = ginkgo.GinkgoRecover
var Describe = ginkgo.Describe
var FDescribe = ginkgo.FDescribe
var PDescribe = ginkgo.PDescribe
var XDescribe = ginkgo.XDescribe
var Context = ginkgo.Context
var FContext = ginkgo.FContext
var PContext = ginkgo.PContext
var XContext = ginkgo.XContext
var When = ginkgo.When
var FWhen = ginkgo.FWhen
var PWhen = ginkgo.PWhen
var XWhen = ginkgo.XWhen
var It = ginkgo.It
var FIt = ginkgo.FIt
var PIt = ginkgo.PIt
var XIt = ginkgo.XIt
var Specify = ginkgo.Specify
var FSpecify = ginkgo.FSpecify
var PSpecify = ginkgo.PSpecify
var XSpecify = ginkgo.XSpecify
var By = ginkgo.By
var BeforeSuite = ginkgo.BeforeSuite
var AfterSuite = ginkgo.AfterSuite
var SynchronizedBeforeSuite = ginkgo.SynchronizedBeforeSuite
var SynchronizedAfterSuite = ginkgo.SynchronizedAfterSuite
var BeforeEach = ginkgo.BeforeEach
var JustBeforeEach = ginkgo.JustBeforeEach
var AfterEach = ginkgo.AfterEach

// Declarations for Gomega DSL
var RegisterFailHandler = gomega.RegisterFailHandler
var RegisterTestingT = gomega.RegisterTestingT
var InterceptGomegaFailures = gomega.InterceptGomegaFailures
var Ω = gomega.Ω
var ExpectWithOffset = gomega.ExpectWithOffset
var EventuallyWithOffset = gomega.EventuallyWithOffset
var ConsistentlyWithOffset = gomega.ConsistentlyWithOffset
var SetDefaultEventuallyTimeout = gomega.SetDefaultEventuallyTimeout
var SetDefaultEventuallyPollingInterval = gomega.SetDefaultEventuallyPollingInterval
var SetDefaultConsistentlyDuration = gomega.SetDefaultConsistentlyDuration
var SetDefaultConsistentlyPollingInterval = gomega.SetDefaultConsistentlyPollingInterval
var NewGomegaWithT = gomega.NewGomegaWithT
var Expect = gomega.Expect
var Eventually = gomega.Eventually
var Consistently = gomega.Consistently

// Declarations for Gomega Matchers
// Equal changed to Equal to avoid conflict
var BeEqualTo = gomega.Equal
var BeEquivalentTo = gomega.BeEquivalentTo
var BeIdenticalTo = gomega.BeIdenticalTo
var BeNil = gomega.BeNil
var BeTrue = gomega.BeTrue
var BeFalse = gomega.BeFalse
var HaveOccurred = gomega.HaveOccurred
var Succeed = gomega.Succeed
var MatchError = gomega.MatchError
var BeClosed = gomega.BeClosed
var Receive = gomega.Receive
var BeSent = gomega.BeSent
var MatchRegexp = gomega.MatchRegexp
var ContainSubstring = gomega.ContainSubstring
var HavePrefix = gomega.HavePrefix
var HaveSuffix = gomega.HaveSuffix
var MatchJSON = gomega.MatchJSON
var MatchXML = gomega.MatchXML
var MatchYAML = gomega.MatchYAML
var BeEmpty = gomega.BeEmpty
var HaveLen = gomega.HaveLen
var HaveCap = gomega.HaveCap
var BeZero = gomega.BeZero
var ContainElement = gomega.ContainElement
var ConsistOf = gomega.ConsistOf
var HaveKey = gomega.HaveKey
var HaveKeyWithValue = gomega.HaveKeyWithValue
var BeNumerically = gomega.BeNumerically
var BeTemporally = gomega.BeTemporally
var BeAssignableToTypeOf = gomega.BeAssignableToTypeOf
var Panic = gomega.Panic
var BeAnExistingFile = gomega.BeAnExistingFile
var BeARegularFile = gomega.BeARegularFile
var BeADirectory = gomega.BeADirectory
var And = gomega.And
var SatisfyAll = gomega.SatisfyAll
var Or = gomega.Or
var SatisfyAny = gomega.SatisfyAny
var Not = gomega.Not
var WithTransform = gomega.WithTransform

type ContextFieldsKey = string

const ContextFields ContextFieldsKey = "ContextFields"

// User model with hash key only
type User struct {
UUID string
Expand All @@ -141,9 +32,3 @@ type Profile struct {
CreatedAt time.Time
TraceID string
}

// WithFields returns context with fields
func WithFields(fields map[string]interface{}) context.Context {
ctx := context.Background()
return context.WithValue(ctx, ContextFields, fields)
}
90 changes: 50 additions & 40 deletions global_index.go → dynamo_global_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,77 @@ package djoemo

import (
"context"
"errors"
"reflect"
"time"

"github.com/guregu/dynamo"
"github.com/prometheus/client_golang/prometheus"
)

// GlobalIndex models a global secondary index used in a query
type GlobalIndex struct {
name string
dynamoClient *dynamo.DB
log logger
log LogInterface
metrics *Metrics
}

// GetItems by key; it accepts a key interface that is used to get the table name, hash key and range key if it exists; the output will be given in items
// returns true if items are found, returns false and nil if no items found, returns false and error in case of error
func (gi GlobalIndex) GetItems(key KeyInterface, items interface{}) (bool, error) {
return gi.GetItemsWithContext(context.TODO(), key, items)
// WithLog enables logging; it accepts LogInterface as logger
func (gi *GlobalIndex) WithLog(log LogInterface) {
gi.log = log
}

// GetItem get item; it accepts a key interface that is used to get the table name, hash key and range key if it exists; the output will be given in item
// returns true if item is found, returns false and nil if no item found, returns false and an error in case of error
func (gi GlobalIndex) GetItem(key KeyInterface, item interface{}) (bool, error) {
return gi.GetItemWithContext(context.TODO(), key, item)
// WithMetrics enables metrics; it accepts MetricsInterface as metrics publisher
func (gi *GlobalIndex) WithMetrics(metricsInterface MetricsInterface) {
gi.metrics.Add(metricsInterface)
}

// WithPrometheusMetrics enables prometheus metrics
func (gi *GlobalIndex) WithPrometheusMetrics(registry *prometheus.Registry) GlobalIndexInterface {
prommetrics := NewPrometheusMetrics(registry)
gi.metrics.Add(prommetrics)
return gi
}

// GetItemWithContext item; it needs a key interface that is used to get the table name, hash key, and the range key if it exists; output will be contained in item; context is optional param, which used to enable log with context
func (gi GlobalIndex) GetItemWithContext(ctx context.Context, key KeyInterface, item interface{}) (bool, error) {
func (gi GlobalIndex) GetItemWithContext(ctx context.Context, key KeyInterface, item any) (bool, error) {
var err error
defer gi.recordMetrics(ctx, OpRead, key, &err)()

if err := isValidKey(key); err != nil {
gi.log.error(ctx, key.TableName(), err.Error())
if err = isValidKey(key); err != nil {
return false, err
}

err := buildTableKeyCondition(gi.table(key.TableName()), key).Index(gi.name).OneWithContext(ctx, item)
err = buildTableKeyCondition(gi.table(key.TableName()), key).Index(gi.name).OneWithContext(ctx, item)
if err != nil {
if err == dynamo.ErrNotFound {
gi.log.info(ctx, key.TableName(), ErrNoItemFound.Error())
if errors.Is(err, dynamo.ErrNotFound) {
gi.log.WithContext(ctx).WithField(TableName, key.TableName()).Info(ErrNoItemFound.Error())
return false, nil
}

gi.log.error(ctx, key.TableName(), err.Error())
return false, err
}

return true, nil

}

// GetItemsWithContext queries multiple items by key (hash key) and returns it in the slice of items items
func (gi GlobalIndex) GetItemsWithContext(ctx context.Context, key KeyInterface, items interface{}) (bool, error) {
if err := isValidKey(key); err != nil {
gi.log.error(ctx, key.TableName(), err.Error())
func (gi GlobalIndex) GetItemsWithContext(ctx context.Context, key KeyInterface, items any) (bool, error) {
var err error
defer gi.recordMetrics(ctx, OpRead, key, &err)()

if err = isValidKey(key); err != nil {
return false, err
}

err := gi.table(key.TableName()).Get(*key.HashKeyName(), key.HashKey()).Index(gi.name).AllWithContext(ctx, items)
err = gi.table(key.TableName()).Get(*key.HashKeyName(), key.HashKey()).Index(gi.name).AllWithContext(ctx, items)
if err != nil {
if err == dynamo.ErrNotFound {
gi.log.info(ctx, key.TableName(), ErrNoItemFound.Error())
if errors.Is(err, dynamo.ErrNotFound) {
gi.log.WithContext(ctx).WithField(TableName, key.TableName()).Info(ErrNoItemFound.Error())
return false, nil
}

gi.log.error(ctx, key.TableName(), err.Error())
return false, err
}

Expand All @@ -82,20 +91,21 @@ func (gi GlobalIndex) GetItemsWithContext(ctx context.Context, key KeyInterface,
}

// GetItemsWithRangeWithContext queries multiple items by key (hash key) and returns it in the slice of items respecting the range key
func (gi GlobalIndex) GetItemsWithRangeWithContext(ctx context.Context, key KeyInterface, items interface{}) (bool, error) {
if err := isValidKey(key); err != nil {
gi.log.error(ctx, key.TableName(), err.Error())
func (gi GlobalIndex) GetItemsWithRangeWithContext(ctx context.Context, key KeyInterface, items any) (bool, error) {
var err error
defer gi.recordMetrics(ctx, OpRead, key, &err)()

if err = isValidKey(key); err != nil {
return false, err
}

err := buildTableKeyCondition(gi.table(key.TableName()), key).Index(gi.name).AllWithContext(ctx, items)
err = buildTableKeyCondition(gi.table(key.TableName()), key).Index(gi.name).AllWithContext(ctx, items)
if err != nil {
if err == dynamo.ErrNotFound {
gi.log.info(ctx, key.TableName(), ErrNoItemFound.Error())
if errors.Is(err, dynamo.ErrNotFound) {
gi.log.WithContext(ctx).WithField(TableName, key.TableName()).Info(ErrNoItemFound.Error())
return false, nil
}

gi.log.error(ctx, key.TableName(), err.Error())
return false, err
}

Expand All @@ -120,13 +130,13 @@ func (gi GlobalIndex) table(tableName string) dynamo.Table {
// QueryWithContext by query; it accepts a query interface that is used to get the table name, hash key and range key with its operator if it exists;
// context which used to enable log with context, the output will be given in items
// returns error in case of error
func (gi GlobalIndex) QueryWithContext(ctx context.Context, query QueryInterface, item interface{}) error {
func (gi GlobalIndex) QueryWithContext(ctx context.Context, query QueryInterface, item any) (err error) {
defer gi.recordMetrics(ctx, OpRead, query, &err)()

if !IsPointerOFSlice(item) {
return ErrInvalidPointerSliceType
}
if err := isValidKey(query); err != nil {
gi.log.error(ctx, query.TableName(), err.Error())
if err = isValidKey(query); err != nil {
return err
}

Expand All @@ -145,17 +155,17 @@ func (gi GlobalIndex) QueryWithContext(ctx context.Context, query QueryInterface
q = q.Order(dynamo.Descending)
}

err := q.AllWithContext(ctx, item)
err = q.AllWithContext(ctx, item)
if err != nil {
gi.log.error(ctx, query.TableName(), err.Error())
return err
}

return nil
}

// Query by query; it accepts a query interface that is used to get the table name, hash key and range key with its operator if it exists;
// returns error in case of error
func (gi GlobalIndex) Query(query QueryInterface, item interface{}) error {
return gi.QueryWithContext(context.TODO(), query, item)
func (gi GlobalIndex) recordMetrics(ctx context.Context, op string, key KeyInterface, err *error) func() {
start := time.Now()
return func() {
gi.metrics.Record(ctx, op, key, time.Since(start), isOpSuccess(err))
}
}
Loading