-
Notifications
You must be signed in to change notification settings - Fork 0
Adding a basic token based algo #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| version: 2 | ||
| updates: | ||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "daily" | ||
| time: "06:00" | ||
| timezone: "Australia/Sydney" | ||
| - package-ecosystem: gomod | ||
| directory: / | ||
| schedule: | ||
| interval: daily | ||
| time: "06:00" | ||
| timezone: "Australia/Sydney" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| name: CI | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branches: | ||
| - main | ||
|
|
||
| jobs: | ||
| check: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout repository | ||
| uses: actions/checkout@v6 | ||
|
|
||
| - name: Set up Go | ||
| uses: actions/setup-go@v6 | ||
| with: | ||
| go-version: "stable" | ||
|
|
||
| - name: Tidy Go modules | ||
| run: go mod tidy | ||
|
|
||
| - name: Check for uncommitted go.mod/go.sum changes | ||
| run: | | ||
| if ! git diff --quiet go.mod; then | ||
| echo "::error::go.mod is not tidy. Run 'go mod tidy' and commit changes." | ||
| git diff go.mod | ||
| exit 1 | ||
| fi | ||
| if [ -f go.sum ] && ! git diff --quiet go.sum; then | ||
| echo "::error::go.sum is not tidy. Run 'go mod tidy' and commit changes." | ||
| git diff go.sum | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Run golangci-lint | ||
| uses: golangci/golangci-lint-action@v9 | ||
|
|
||
| - name: Run tests | ||
| run: go test -v -race ./... | ||
|
|
||
| - name: Install go-test-coverage | ||
| run: go install github.com/vladopajic/go-test-coverage/v2@latest | ||
|
|
||
| - name: Run Tests with Coverage | ||
| run: go test ./... -coverprofile=coverage.out -covermode=atomic | ||
|
|
||
| - name: Check Coverage Threshold (95%) | ||
| run: go-test-coverage --config=.testcoverage.yml |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| version: "2" | ||
| linters: | ||
| enable: | ||
| - asasalint | ||
| - asciicheck | ||
| - bidichk | ||
| - bodyclose | ||
| - canonicalheader | ||
| - containedctx | ||
| - contextcheck | ||
| - copyloopvar | ||
| - cyclop | ||
| - decorder | ||
| - dogsled | ||
| - dupl | ||
| - dupword | ||
| - durationcheck | ||
| - errchkjson | ||
| - errname | ||
| - errorlint | ||
| - exhaustive | ||
| - exptostd | ||
| - fatcontext | ||
| - forbidigo | ||
| - funlen | ||
| - ginkgolinter | ||
| - gocheckcompilerdirectives | ||
| - gochecknoinits | ||
| - gochecksumtype | ||
| - gocognit | ||
| - goconst | ||
| - gocritic | ||
| - gocyclo | ||
| - godot | ||
| - godox | ||
| - goheader | ||
| - gomoddirectives | ||
| - gomodguard | ||
| - goprintffuncname | ||
| - gosec | ||
| - gosmopolitan | ||
| - grouper | ||
| - iface | ||
| - importas | ||
| - interfacebloat | ||
| - intrange | ||
| - lll | ||
| - loggercheck | ||
| - maintidx | ||
| - makezero | ||
| - mirror | ||
| - misspell | ||
| - musttag | ||
| - nakedret | ||
| - nestif | ||
| - nilerr | ||
| - nilnesserr | ||
| - nilnil | ||
| - nlreturn | ||
| - noctx | ||
| - nolintlint | ||
| - nosprintfhostport | ||
| - perfsprint | ||
| - prealloc | ||
| - predeclared | ||
| - promlinter | ||
| - reassign | ||
| - recvcheck | ||
| - revive | ||
| - rowserrcheck | ||
| - sloglint | ||
| - spancheck | ||
| - sqlclosecheck | ||
| - staticcheck | ||
| - tagalign | ||
| - tagliatelle | ||
| - testableexamples | ||
| - testifylint | ||
| - testpackage | ||
| - thelper | ||
| - tparallel | ||
| - unconvert | ||
| - unparam | ||
| - usestdlibvars | ||
| - usetesting | ||
| - wastedassign | ||
| - whitespace | ||
| - wsl_v5 | ||
| - zerologlint | ||
| settings: | ||
| wsl_v5: | ||
| allow-first-in-block: true | ||
| allow-whole-block: false | ||
| branch-max-lines: 2 | ||
| exclusions: | ||
| generated: lax | ||
| presets: | ||
| - comments | ||
| - common-false-positives | ||
| - legacy | ||
| - std-error-handling | ||
| paths: | ||
| - third_party$ | ||
| - builtin$ | ||
| - examples$ | ||
| rules: | ||
| - path: _test\.go$ | ||
| linters: | ||
| - funlen | ||
| - dupl | ||
| - path: internal/app/.*\.go$ | ||
| linters: | ||
| - funlen | ||
| - dupl | ||
| formatters: | ||
| enable: | ||
| - gci | ||
| - gofmt | ||
| - gofumpt | ||
| exclusions: | ||
| generated: lax | ||
| paths: | ||
| - third_party$ | ||
| - builtin$ | ||
| - examples$ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| # Configuration for go-test-coverage | ||
| # See: https://github.com/vladopajic/go-test-coverage | ||
|
|
||
| profile: coverage.out | ||
|
|
||
| threshold: | ||
| # Total coverage threshold | ||
| total: 95 | ||
|
|
||
| # Per-file thresholds (optional) | ||
| file: 0 | ||
|
|
||
| # Exclude patterns - these packages won't count toward coverage | ||
| exclude: | ||
| # Main entry points and DI wiring | ||
| paths: | ||
| - ^cmd/ | ||
| - ^internal/container/ | ||
| - ^internal/handlers/routes\.go$ | ||
| # Infrastructure stores tested via integration tests | ||
| - ^internal/store/redis\.go$ | ||
| - ^internal/store/postgres\.go$ | ||
| - ^internal/store/redis_cache\.go$ | ||
| - ^internal/analytics/store/postgres\.go$ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| module github.com/serroba/rate | ||
|
|
||
| go 1.25 | ||
|
|
||
| require github.com/stretchr/testify v1.11.1 | ||
|
|
||
| require ( | ||
| github.com/davecgh/go-spew v1.1.1 // indirect | ||
| github.com/pmezard/go-difflib v1.0.0 // indirect | ||
| gopkg.in/yaml.v3 v3.0.1 // indirect | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
| github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= | ||
| github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= | ||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,76 @@ | ||||||||||||||
| package token | ||||||||||||||
|
|
||||||||||||||
| import ( | ||||||||||||||
| "errors" | ||||||||||||||
| "time" | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| type clock interface { | ||||||||||||||
| Now() time.Time | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| type realClock struct{} | ||||||||||||||
|
|
||||||||||||||
| func (c realClock) Now() time.Time { | ||||||||||||||
| return time.Now() | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| // Limiter implements a token bucket rate limiter. It allows a burst of | ||||||||||||||
| // requests up to capacity, then refills tokens at the specified rate per second. | ||||||||||||||
| type Limiter struct { | ||||||||||||||
| capacity, tokens, rate float64 | ||||||||||||||
| lastRefillAt time.Time | ||||||||||||||
| clock clock | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+20
to
+24
|
||||||||||||||
|
|
||||||||||||||
|
||||||||||||||
| // NewLimiter creates a token-bucket limiter with the given capacity and rate. | |
| // capacity is the maximum number of tokens that can be accumulated in the bucket, | |
| // and rate is the number of tokens added to the bucket per second. | |
| // The returned Limiter is initialized with a real-time clock and starts full. |
Copilot
AI
Dec 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public NewLimiterWithClock function lacks documentation. According to Go conventions, exported functions should have a comment describing their purpose. Consider adding a comment explaining this is primarily for testing purposes and what each parameter represents.
| // NewLimiterWithClock creates a Limiter with the given capacity and refill rate, | |
| // using the provided Clock implementation. This is primarily intended for tests | |
| // that need to control the passage of time. |
Copilot
AI
Dec 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public Allow method lacks documentation. According to Go conventions, exported methods should have a comment describing their purpose and behavior. Consider adding a comment explaining that it attempts to consume one token and returns whether the operation was successful.
| // Allow attempts to consume one token from the limiter and reports whether it succeeded. |
Copilot
AI
Dec 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Limiter is not thread-safe. The Allow method reads and modifies shared state (tokens, lastRefillAt) without synchronization, which can lead to race conditions when accessed from multiple goroutines. Consider adding a mutex (sync.Mutex) to protect access to these fields, especially since rate limiters are commonly used in concurrent scenarios.
Copilot
AI
Dec 31, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The refill method modifies shared state without synchronization, which will cause race conditions in concurrent usage. This method should be protected by the same mutex that guards the Allow method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The public Clock interface lacks documentation. According to Go conventions, exported types should have a comment describing their purpose. Consider adding a comment like "// Clock provides the current time, allowing for time injection in tests."