Showing preview only (956K chars total). Download the full file or copy to clipboard to get everything.
Repository: gofiber/contrib
Branch: main
Commit: 09779e8bbddf
Files: 200
Total size: 900.7 KB
Directory structure:
gitextract_6_bal560/
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yaml
│ │ ├── feature-request.yaml
│ │ └── question.yaml
│ ├── dependabot.yml
│ ├── release-plan.yml
│ ├── release.yml
│ ├── scripts/
│ │ └── parallel-go-test.sh
│ └── workflows/
│ ├── after-release.yml
│ ├── auto-labeler.yml
│ ├── cleanup-release-draft.yml
│ ├── dependabot-on-demand.yml
│ ├── dependabot_automerge.yml
│ ├── lint.yml
│ ├── release-drafter.yml
│ ├── sync-docs.yml
│ ├── test-casbin.yml
│ ├── test-circuitbreaker.yml
│ ├── test-coraza.yml
│ ├── test-fgprof.yml
│ ├── test-hcaptcha.yml
│ ├── test-i18n.yml
│ ├── test-jwt.yml
│ ├── test-loadshed.yml
│ ├── test-monitor.yml
│ ├── test-newrelic.yml
│ ├── test-opa.yml
│ ├── test-otel.yml
│ ├── test-paseto.yml
│ ├── test-sentry.yml
│ ├── test-socketio.yml
│ ├── test-swaggerui.yml
│ ├── test-swaggo.yml
│ ├── test-testcontainers.yml
│ ├── test-websocket.yml
│ ├── test-zap.yml
│ ├── test-zerolog.yml
│ └── weekly-release.yml
├── .gitignore
├── LICENSE
├── README.md
├── go.work
└── v3/
├── .golangci.yml
├── README.md
├── casbin/
│ ├── README.md
│ ├── casbin.go
│ ├── casbin_test.go
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── options.go
│ └── utils.go
├── circuitbreaker/
│ ├── README.md
│ ├── circuitbreaker.go
│ ├── circuitbreaker_test.go
│ ├── go.mod
│ └── go.sum
├── coraza/
│ ├── README.md
│ ├── coraza.go
│ ├── coraza_test.go
│ ├── go.mod
│ ├── go.sum
│ └── metrics.go
├── fgprof/
│ ├── README.md
│ ├── config.go
│ ├── fgprof.go
│ ├── fgprof_test.go
│ ├── go.mod
│ └── go.sum
├── hcaptcha/
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── hcaptcha.go
│ └── hcaptcha_test.go
├── i18n/
│ ├── README.md
│ ├── config.go
│ ├── embed.go
│ ├── embed_test.go
│ ├── example/
│ │ ├── localize/
│ │ │ ├── en.yaml
│ │ │ └── zh.yaml
│ │ ├── localizeJSON/
│ │ │ ├── en.json
│ │ │ └── zh.json
│ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── i18n.go
│ └── i18n_test.go
├── jwt/
│ ├── README.md
│ ├── config.go
│ ├── config_test.go
│ ├── crypto.go
│ ├── go.mod
│ ├── go.sum
│ ├── jwt.go
│ └── jwt_test.go
├── loadshed/
│ ├── README.md
│ ├── cpu.go
│ ├── go.mod
│ ├── go.sum
│ ├── loadshed.go
│ └── loadshed_test.go
├── monitor/
│ ├── README.md
│ ├── config.go
│ ├── config_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── index.go
│ ├── monitor.go
│ └── monitor_test.go
├── newrelic/
│ ├── README.md
│ ├── fiber.go
│ ├── fiber_test.go
│ ├── go.mod
│ └── go.sum
├── opa/
│ ├── README.md
│ ├── fiber.go
│ ├── fiber_test.go
│ ├── go.mod
│ └── go.sum
├── otel/
│ ├── README.md
│ ├── config.go
│ ├── doc.go
│ ├── example/
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── docker-compose.yml
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── server.go
│ ├── fiber.go
│ ├── fiber_context_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── internal/
│ │ ├── http.go
│ │ └── http_test.go
│ ├── otel_test/
│ │ └── fiber_test.go
│ └── semconv.go
├── paseto/
│ ├── README.md
│ ├── config.go
│ ├── config_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── helpers.go
│ ├── paseto.go
│ ├── paseto_test.go
│ └── payload.go
├── sentry/
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── sentry.go
│ └── sentry_test.go
├── socketio/
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ ├── socketio.go
│ └── socketio_test.go
├── swaggerui/
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ ├── swagger.go
│ ├── swagger.json
│ ├── swagger.yaml
│ ├── swagger_missing.json
│ └── swagger_test.go
├── swaggo/
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── index.go
│ ├── swagger.go
│ └── swagger_test.go
├── testcontainers/
│ ├── README.md
│ ├── config.go
│ ├── examples_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── testcontainers.go
│ ├── testcontainers_test.go
│ └── testcontainers_unit_test.go
├── websocket/
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ ├── websocket.go
│ └── websocket_test.go
├── zap/
│ ├── .gitignore
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── logger.go
│ ├── logger_test.go
│ ├── zap.go
│ └── zap_test.go
└── zerolog/
├── README.md
├── config.go
├── go.mod
├── go.sum
├── zerolog.go
└── zerolog_test.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODEOWNERS
================================================
* @gofiber/maintainers
================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.yaml
================================================
name: "\U0001F41B Bug Report"
title: "\U0001F41B [Bug]: "
description: Create a bug report to help us fix it.
labels: ["☢️ Bug"]
body:
- type: markdown
id: notice
attributes:
value: |
### Notice
- Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord).
- If you think Fiber contrib don't have a nice feature that you think, open the issue with **✏️ Feature Request** template.
- Write your issue with clear and understandable English.
- type: textarea
id: description
attributes:
label: "Bug Description"
description: "A clear and detailed description of what the bug is."
placeholder: "Explain your problem as clear and detailed."
validations:
required: true
- type: textarea
id: how-to-reproduce
attributes:
label: How to Reproduce
description: "Steps to reproduce the behavior and what should be observed in the end."
placeholder: "Tell us step by step how we can replicate your problem and what we should see in the end."
value: |
Steps to reproduce the behavior:
1. Go to '....'
2. Click on '....'
3. Do '....'
4. See '....'
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected Behavior
description: "A clear and detailed description of what you think should happens."
placeholder: "Tell us what contrib should normally do."
validations:
required: true
- type: input
id: version
attributes:
label: "Contrib package Version"
description: "Some bugs may be fixed in future contrib releases, so we have to know your contrib package version."
placeholder: "Write your contrib version. (v1.0.0, v1.1.0...)"
validations:
required: true
- type: textarea
id: snippet
attributes:
label: "Code Snippet (optional)"
description: "For some issues, we need to know some parts of your code."
placeholder: "Share a code you think related to the issue."
render: go
value: |
package main
// Replace <MAJOR> with the module's current major version (omit the `/v<MAJOR>` suffix entirely for v1 modules).
import "github.com/gofiber/contrib/v3/%package%"
func main() {
// Steps to reproduce
}
- type: checkboxes
id: terms
attributes:
label: "Checklist:"
description: "By submitting this issue, you confirm that:"
options:
- label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)."
required: true
- label: "I have checked for existing issues that describe my problem prior to opening this one."
required: true
- label: "I understand that improperly formatted bug reports may be closed without explanation."
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.yaml
================================================
name: "\U0001F680 Feature Request"
title: "\U0001F680 [Feature]: "
description: Suggest an idea to improve this project.
labels: ["✏️ Feature"]
body:
- type: markdown
id: notice
attributes:
value: |
### Notice
- Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord).
- If you think this is just a bug, open the issue with **☢️ Bug Report** template.
- Write your issue with clear and understandable English.
- type: textarea
id: description
attributes:
label: "Feature Description"
description: "A clear and detailed description of the feature we need to do."
placeholder: "Explain your feature as clear and detailed."
validations:
required: true
- type: textarea
id: additional-context
attributes:
label: "Additional Context (optional)"
description: "If you have something else to describe, write them here."
placeholder: "Write here what you can describe differently."
- type: textarea
id: snippet
attributes:
label: "Code Snippet (optional)"
description: "Code snippet may be really helpful to describe some features."
placeholder: "Share a code to explain the feature better."
render: go
value: |
package main
// Replace <MAJOR> with the module's current major version (omit the `/v<MAJOR>` suffix entirely for v1 modules).
import "github.com/gofiber/contrib/v3/%package%"
func main() {
// Steps to reproduce
}
- type: checkboxes
id: terms
attributes:
label: "Checklist:"
description: "By submitting this issue, you confirm that:"
options:
- label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)."
required: true
- label: "I have checked for existing issues that describe my suggestion prior to opening this one."
required: true
- label: "I understand that improperly formatted feature requests may be closed without explanation."
required: true
================================================
FILE: .github/ISSUE_TEMPLATE/question.yaml
================================================
name: "🤔 Question"
title: "\U0001F917 [Question]: "
description: Ask a question so we can help you easily.
labels: ["🤔 Question"]
body:
- type: markdown
id: notice
attributes:
value: |
### Notice
- Dont't forget you can ask your questions on our [Discord server](https://gofiber.io/discord).
- If you think this is just a bug, open the issue with **☢️ Bug Report** template.
- If you think Fiber contrib don't have a nice feature that you think, open the issue with **✏️ Feature Request** template.
- Write your issue with clear and understandable English.
- type: textarea
id: description
attributes:
label: "Question Description"
description: "A clear and detailed description of the question."
placeholder: "Explain your question as clear and detailed."
validations:
required: true
- type: textarea
id: snippet
attributes:
label: "Code Snippet (optional)"
description: "Code snippet may be really helpful to describe some features."
placeholder: "Share a code to explain the feature better."
render: go
value: |
package main
// Replace <MAJOR> with the module's current major version (omit the `/v<MAJOR>` suffix entirely for v1 modules).
import "github.com/gofiber/contrib/v3/%package%"
func main() {
// Steps to reproduce
}
- type: checkboxes
id: terms
attributes:
label: "Checklist:"
description: "By submitting this issue, you confirm that:"
options:
- label: "I agree to follow Fiber's [Code of Conduct](https://github.com/gofiber/fiber/blob/master/.github/CODE_OF_CONDUCT.md)."
required: true
- label: "I have checked for existing issues that describe my questions prior to opening this one."
required: true
- label: "I understand that improperly formatted questions may be closed without explanation."
required: true
================================================
FILE: .github/dependabot.yml
================================================
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#directories
version: 2
updates:
- package-ecosystem: "github-actions"
open-pull-requests-limit: 50
directory: "/"
labels:
- "🤖 Dependencies"
schedule:
interval: "daily"
# gomod split by alphabet ranges to reduce group PR size
# a-l ~8 modules | m-r ~6 modules | s-z ~8 modules
- package-ecosystem: "gomod"
open-pull-requests-limit: 20
allow:
- dependency-type: "all"
directories:
- "/v3/a*"
- "/v3/b*"
- "/v3/c*"
- "/v3/d*"
- "/v3/e*"
- "/v3/f*"
- "/v3/g*"
- "/v3/h*"
- "/v3/i*"
- "/v3/j*"
- "/v3/k*"
- "/v3/l*"
labels:
- "🤖 Dependencies"
schedule:
interval: "daily"
groups:
fiber-modules:
patterns:
- "github.com/gofiber/fiber/**"
fiber-utils-modules:
patterns:
- "github.com/gofiber/utils"
- "github.com/gofiber/utils/**"
fiber-schema-modules:
patterns:
- "github.com/gofiber/schema"
- "github.com/gofiber/schema/**"
fasthttp-modules:
patterns:
- "github.com/valyala/fasthttp"
- "github.com/valyala/fasthttp/**"
golang-modules:
patterns:
- "golang.org/x/**"
opentelemetry-modules:
patterns:
- "go.opentelemetry.io/**"
fasthttp-websocket-modules:
patterns:
- "github.com/fasthttp/websocket/**"
valyala-utils-modules:
patterns:
- "github.com/valyala/bytebufferpool"
- "github.com/valyala/tcplisten"
mattn-modules:
patterns:
- "github.com/mattn/go-colorable"
- "github.com/mattn/go-isatty"
- "github.com/mattn/go-runewidth"
shirou-modules:
patterns:
- "github.com/shirou/gopsutil"
- "github.com/shirou/gopsutil/**"
ebitengine-modules:
patterns:
- "github.com/ebitengine/**"
klauspost-modules:
patterns:
- "github.com/klauspost/**"
tklauser-modules:
patterns:
- "github.com/tklauser/**"
google-modules:
patterns:
- "github.com/google/**"
- "google.golang.org/**"
testing-modules:
patterns:
- "github.com/stretchr/**"
- "github.com/davecgh/go-spew"
- "github.com/pmezard/go-difflib"
check-testing-modules:
patterns:
- "gopkg.in/check.v*"
- "github.com/kr/**"
- "github.com/rogpeppe/go-internal"
tinylib-modules:
patterns:
- "github.com/tinylib/**"
msgp-modules:
patterns:
- "github.com/philhofer/fwd"
openapi-modules:
patterns:
- "github.com/go-openapi/**"
yaml-modules:
patterns:
- "gopkg.in/yaml.*"
- "go.yaml.in/yaml/**"
- "sigs.k8s.io/yaml"
andybalholm-modules:
patterns:
- "github.com/andybalholm/**"
- package-ecosystem: "gomod"
open-pull-requests-limit: 20
allow:
- dependency-type: "all"
directories:
- "/v3/m*"
- "/v3/n*"
- "/v3/o*"
- "/v3/o*/*"
- "/v3/p*"
- "/v3/q*"
- "/v3/r*"
labels:
- "🤖 Dependencies"
schedule:
interval: "daily"
groups:
fiber-modules:
patterns:
- "github.com/gofiber/fiber/**"
fiber-utils-modules:
patterns:
- "github.com/gofiber/utils"
- "github.com/gofiber/utils/**"
fiber-schema-modules:
patterns:
- "github.com/gofiber/schema"
- "github.com/gofiber/schema/**"
fasthttp-modules:
patterns:
- "github.com/valyala/fasthttp"
- "github.com/valyala/fasthttp/**"
golang-modules:
patterns:
- "golang.org/x/**"
opentelemetry-modules:
patterns:
- "go.opentelemetry.io/**"
fasthttp-websocket-modules:
patterns:
- "github.com/fasthttp/websocket/**"
valyala-utils-modules:
patterns:
- "github.com/valyala/bytebufferpool"
- "github.com/valyala/tcplisten"
mattn-modules:
patterns:
- "github.com/mattn/go-colorable"
- "github.com/mattn/go-isatty"
- "github.com/mattn/go-runewidth"
shirou-modules:
patterns:
- "github.com/shirou/gopsutil"
- "github.com/shirou/gopsutil/**"
ebitengine-modules:
patterns:
- "github.com/ebitengine/**"
klauspost-modules:
patterns:
- "github.com/klauspost/**"
tklauser-modules:
patterns:
- "github.com/tklauser/**"
google-modules:
patterns:
- "github.com/google/**"
- "google.golang.org/**"
testing-modules:
patterns:
- "github.com/stretchr/**"
- "github.com/davecgh/go-spew"
- "github.com/pmezard/go-difflib"
check-testing-modules:
patterns:
- "gopkg.in/check.v*"
- "github.com/kr/**"
- "github.com/rogpeppe/go-internal"
tinylib-modules:
patterns:
- "github.com/tinylib/**"
msgp-modules:
patterns:
- "github.com/philhofer/fwd"
openapi-modules:
patterns:
- "github.com/go-openapi/**"
yaml-modules:
patterns:
- "gopkg.in/yaml.*"
- "go.yaml.in/yaml/**"
- "sigs.k8s.io/yaml"
andybalholm-modules:
patterns:
- "github.com/andybalholm/**"
- package-ecosystem: "gomod"
open-pull-requests-limit: 20
allow:
- dependency-type: "all"
directories:
- "/v3/s*"
- "/v3/t*"
- "/v3/u*"
- "/v3/v*"
- "/v3/w*"
- "/v3/x*"
- "/v3/y*"
- "/v3/z*"
labels:
- "🤖 Dependencies"
schedule:
interval: "daily"
groups:
fiber-modules:
patterns:
- "github.com/gofiber/fiber/**"
fiber-utils-modules:
patterns:
- "github.com/gofiber/utils"
- "github.com/gofiber/utils/**"
fiber-schema-modules:
patterns:
- "github.com/gofiber/schema"
- "github.com/gofiber/schema/**"
fasthttp-modules:
patterns:
- "github.com/valyala/fasthttp"
- "github.com/valyala/fasthttp/**"
golang-modules:
patterns:
- "golang.org/x/**"
opentelemetry-modules:
patterns:
- "go.opentelemetry.io/**"
fasthttp-websocket-modules:
patterns:
- "github.com/fasthttp/websocket/**"
valyala-utils-modules:
patterns:
- "github.com/valyala/bytebufferpool"
- "github.com/valyala/tcplisten"
mattn-modules:
patterns:
- "github.com/mattn/go-colorable"
- "github.com/mattn/go-isatty"
- "github.com/mattn/go-runewidth"
shirou-modules:
patterns:
- "github.com/shirou/gopsutil"
- "github.com/shirou/gopsutil/**"
ebitengine-modules:
patterns:
- "github.com/ebitengine/**"
klauspost-modules:
patterns:
- "github.com/klauspost/**"
tklauser-modules:
patterns:
- "github.com/tklauser/**"
google-modules:
patterns:
- "github.com/google/**"
- "google.golang.org/**"
testing-modules:
patterns:
- "github.com/stretchr/**"
- "github.com/davecgh/go-spew"
- "github.com/pmezard/go-difflib"
check-testing-modules:
patterns:
- "gopkg.in/check.v*"
- "github.com/kr/**"
- "github.com/rogpeppe/go-internal"
tinylib-modules:
patterns:
- "github.com/tinylib/**"
msgp-modules:
patterns:
- "github.com/philhofer/fwd"
openapi-modules:
patterns:
- "github.com/go-openapi/**"
yaml-modules:
patterns:
- "gopkg.in/yaml.*"
- "go.yaml.in/yaml/**"
- "sigs.k8s.io/yaml"
andybalholm-modules:
patterns:
- "github.com/andybalholm/**"
================================================
FILE: .github/release-plan.yml
================================================
# Release plan for gofiber/contrib.
#
# Only modules with ordering constraints need explicit waves.
# The final wave with `auto-discover: true` picks up all remaining
# draft releases that weren't handled by earlier waves.
#
# Constraint: socketio depends on websocket (local replace).
# Websocket's post-release hook triggers dependabot in this repo
# immediately so socketio picks up the new version before wave 2.
#
# After all modules are published, 'after-release' is dispatched once.
# The after-release.yml workflow defines which repos to notify.
wave-delay-minutes: 30
waves:
- name: base
modules:
- name: websocket
tag-prefix: "v3/websocket/"
post-release:
- action: dispatch
repo: gofiber/contrib
event: dependabot-on-demand
- name: remaining
auto-discover: true
post-release:
- action: dispatch
event: after-release
- action: dispatch
event: sync-docs
================================================
FILE: .github/release.yml
================================================
# .github/release.yml
changelog:
categories:
- title: '❗ Breaking Changes'
labels:
- '❗ BreakingChange'
- title: '🚀 New Features'
labels:
- '✏️ Feature'
- '📝 Proposal'
- title: '🧹 Updates'
labels:
- '🧹 Updates'
- '⚡️ Performance'
- title: '🐛 Bug Fixes'
labels:
- '☢️ Bug'
- title: '🛠️ Maintenance'
labels:
- '🤖 Dependencies'
- title: '📚 Documentation'
labels:
- '📒 Documentation'
- title: 'Other Changes'
labels:
- '*'
================================================
FILE: .github/scripts/parallel-go-test.sh
================================================
#!/usr/bin/env bash
# parallel-go-test.sh (refreshed)
# Recursively find go.mod files under the current directory and run tests in each module.
# - truncates tests-errors.log at start
# - runs tests in parallel (jobs = CPU cores)
# - prefers `gotestsum` when available; otherwise uses `go test`
# - on failures, appends annotated error blocks (source line + one-line context) to tests-errors.log immediately
# - filters out successful test noise (=== RUN / --- PASS / PASS / ok ...)
# - atomic appends using mkdir-lock so parallel jobs don't interleave
# - colored terminal output (OK = green, FAIL = red, WARN = yellow)
# - handles Ctrl+C / SIGTERM: kills children and cleans up
# - option: -m to run `go mod download` and `go mod vendor` in each module before tests
set -euo pipefail
IFS=$'\n\t'
JOBS="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4)"
LOGFILE="tests-errors.log"
RUN_GOMOD=0
usage() {
cat <<'USAGE'
Usage: ./parallel-go-test.sh [-j JOBS] [-o LOGFILE] [-m]
Options:
-j JOBS Number of parallel jobs (defaults to CPU cores)
-o LOGFILE Path to central logfile (defaults to tests-errors.log)
-m Run `go mod download` and `go mod vendor` in each module before running tests
Examples:
./parallel-go-test.sh # default behaviour
./parallel-go-test.sh -m # run `go mod download` + `go mod vendor` first
./parallel-go-test.sh -j 8 -m # 8 parallel jobs and run mod step
USAGE
}
# parse options
while getopts ":j:o:mh" opt; do
case "$opt" in
j) JOBS="$OPTARG" ;;
o) LOGFILE="$OPTARG" ;;
m) RUN_GOMOD=1 ;;
h) usage; exit 0 ;;
\?) printf 'Unknown option: -%s\n' "$OPTARG"; usage; exit 2 ;;
:) printf 'Option -%s requires an argument.\n' "$OPTARG"; usage; exit 2 ;;
esac
done
shift $((OPTIND -1))
# Colors (only if stdout is a tty)
if [ -t 1 ]; then
GREEN=$'\e[32m'
RED=$'\e[31m'
YELLOW=$'\e[33m'
RESET=$'\e[0m'
else
GREEN=""
RED=""
YELLOW=""
RESET=""
fi
# truncate central logfile at start
: > "$LOGFILE"
# detect gotestsum
USE_GOTESTSUM=0
if command -v gotestsum >/dev/null 2>&1; then
USE_GOTESTSUM=1
fi
# find all module directories (skip common vendor trees)
mapfile -t MODULE_DIRS < <(
find . \( -path "./.git" -o -path "./vendor" -o -path "./node_modules" \) -prune -o \
-type f -name 'go.mod' -print0 |
xargs -0 -n1 dirname |
sort -u
)
if [ "${#MODULE_DIRS[@]}" -eq 0 ]; then
printf '%s\n' "No go.mod files found. Nothing to do."
exit 0
fi
# create absolute temp dir
TMPDIR="$(mktemp -d 2>/dev/null || mktemp -d /tmp/tests-logs.XXXXXX)"
if [ -z "$TMPDIR" ] || [ ! -d "$TMPDIR" ]; then
printf '%s\n' "Failed to create temporary directory" >&2
exit 2
fi
# cleanup routine
cleanup_tmpdir() {
local attempts=0
while [ "$attempts" -lt 5 ]; do
if rm -rf "$TMPDIR" 2>/dev/null; then
break
fi
attempts=$((attempts+1))
sleep 0.1
done
if [ -d "$TMPDIR" ]; then
printf '%s\n' "WARNING: could not fully remove temporary dir: $TMPDIR"
fi
}
# signal handling: kill children, wait, cleanup, exit
pids=()
on_interrupt() {
printf '%b\n' "${YELLOW}Received interrupt. Killing background jobs...${RESET}"
for pid in "${pids[@]:-}"; do
if kill -0 "$pid" 2>/dev/null; then
kill "$pid" 2>/dev/null || true
fi
done
sleep 0.1
for pid in "${pids[@]:-}"; do
if kill -0 "$pid" 2>/dev/null; then
kill -9 "$pid" 2>/dev/null || true
fi
done
cleanup_tmpdir
printf '%b\n' "${YELLOW}Aborted by user.${RESET}"
exit 130
}
trap on_interrupt INT TERM
# helper: sanitize a dir to a filename-safe token
sanitize_name() {
local d="$1"
d="${d#./}"
d="${d//\//__}"
d="${d// /_}"
printf '%s' "${d//[^A-Za-z0-9._-]/_}"
}
# annotate a module's temp log and append to central logfile atomically
# This version: only appends when failure indicators are present and strips PASS/RUN noise
annotate_and_append() {
local src_log="$1"
local module_dir="$2"
local lockdir="$TMPDIR/.lock"
# quick check: only append logs that contain failure indicators
if ! grep -E -q '(--- FAIL:|^FAIL\b|panic:|exit status|FAIL\t|FAIL:)' "$src_log"; then
# nothing to do (only PASS/ok output)
rm -f "$src_log" >/dev/null 2>&1 || true
return
fi
local annotated
annotated="$(mktemp "$TMPDIR/annotated.XXXXXX")" || {
until mkdir "$lockdir" 2>/dev/null; do sleep 0.01; done
printf '==== %s ====\n' "$module_dir" >> "$LOGFILE"
# filter out PASS/OK noise when falling back
grep -v -E '^(=== RUN|--- PASS:|^PASS$|^ok\s)' "$src_log" >> "$LOGFILE" || true
rm -f "$src_log" || true
rmdir "$lockdir" 2>/dev/null || true
return
}
# process lines in src_log, skipping PASS/RUN lines and annotating file:line occurrences
while IFS= read -r line || [ -n "$line" ]; do
# skip successful-test noise
if [[ $line =~ ^(===\ RUN|---\ PASS:|^PASS$|^ok\s) ]]; then
continue
fi
# match paths like path/to/file.go:LINE or file.go:LINE:COL
if [[ $line =~ ^([^:]+\.go):([0-9]+):?([0-9]*)[:[:space:]]*(.*)$ ]]; then
local fp="${BASH_REMATCH[1]}"
local ln="${BASH_REMATCH[2]}"
local col="${BASH_REMATCH[3]}"
local rest="${BASH_REMATCH[4]}"
local candidate=""
if [ -f "$fp" ]; then
candidate="$fp"
elif [ -f "$module_dir/$fp" ]; then
candidate="$module_dir/$fp"
elif [ -f "./$fp" ]; then
candidate="./$fp"
fi
if [ -n "$candidate" ]; then
local start=$(( ln > 1 ? ln - 1 : 1 ))
local end=$(( ln + 1 ))
printf '%s\n' "---- source: $candidate:$ln ----" >> "$annotated"
awk -v s="$start" -v e="$end" 'NR>=s && NR<=e { printf("%6d %s\n", NR, $0) }' "$candidate" >> "$annotated"
printf '%s\n\n' "Error: $line" >> "$annotated"
else
printf '%s\n' "---- (source not found) $line ----" >> "$annotated"
fi
else
printf '%s\n' "$line" >> "$annotated"
fi
done < "$src_log"
# append atomically under lock
until mkdir "$lockdir" 2>/dev/null; do
sleep 0.01
done
{
printf '==== %s ====\n' "$module_dir"
cat "$annotated"
printf '\n\n'
} >> "$LOGFILE"
rm -f "$annotated" "$src_log" || true
rmdir "$lockdir" 2>/dev/null || true
}
# run tests for one module (with optional go mod download + vendor step)
run_tests() {
local module_dir="$1"
local safe
safe="$(sanitize_name "$module_dir")"
local mod_log="$TMPDIR/$safe.log"
mkdir -p "$(dirname "$mod_log")"
# optionally run `go mod download` and `go mod vendor` first
if [ "$RUN_GOMOD" -eq 1 ]; then
local mod_step_log="$TMPDIR/$safe.modlog"
: > "$mod_step_log"
( cd "$module_dir" 2>/dev/null && go mod download >>"$mod_step_log" 2>&1 ) || true
( cd "$module_dir" 2>/dev/null && go mod vendor >>"$mod_step_log" 2>&1 ) || true
if [ -s "$mod_step_log" ]; then
printf '%b\n' "${YELLOW}MOD: ${RESET}$module_dir (go mod output appended)"
annotate_and_append "$mod_step_log" "$module_dir"
else
rm -f "$mod_step_log" >/dev/null 2>&1 || true
fi
fi
# run tests: always capture stdout+stderr to per-module log so nothing prints on success
if [ "$USE_GOTESTSUM" -eq 1 ]; then
# use gotestsum but still capture its output to file
if ( cd "$module_dir" 2>/dev/null && gotestsum --format standard-verbose -- -test.v ./... >"$mod_log" 2>&1 ); then
printf '%b\n' "${GREEN}OK: ${RESET}$module_dir"
rm -f "$mod_log" >/dev/null 2>&1 || true
return 0
else
printf '%b\n' "${RED}FAIL: ${RESET}$module_dir (appending to $LOGFILE)"
annotate_and_append "$mod_log" "$module_dir"
return 1
fi
else
# fallback to go test; capture everything
if ( cd "$module_dir" 2>/dev/null && go test ./... -run "" -v >"$mod_log" 2>&1 ); then
printf '%b\n' "${GREEN}OK: ${RESET}$module_dir"
rm -f "$mod_log" >/dev/null 2>&1 || true
return 0
else
printf '%b\n' "${RED}FAIL: ${RESET}$module_dir (appending to $LOGFILE)"
annotate_and_append "$mod_log" "$module_dir"
return 1
fi
fi
}
# main launcher: spawn jobs, throttle to JOBS, wait properly
fail_count=0
for md in "${MODULE_DIRS[@]}"; do
run_tests "$md" &
pids+=( "$!" )
if [ "${#pids[@]}" -ge "$JOBS" ]; then
if wait "${pids[0]}"; then
:
else
fail_count=$((fail_count+1))
fi
pids=( "${pids[@]:1}" )
fi
done
# wait remaining background jobs
for pid in "${pids[@]:-}"; do
if wait "$pid"; then :; else fail_count=$((fail_count+1)); fi
done
# final cleanup and status
cleanup_tmpdir
if [ "$fail_count" -gt 0 ]; then
printf '\n%b\n' "${RED}Done. $fail_count module(s) failed. See $LOGFILE for details (annotated snippets included).${RESET}"
exit 1
else
printf '\n%b\n' "${GREEN}Done. All modules tested successfully.${RESET}"
if [ -f "$LOGFILE" ] && [ ! -s "$LOGFILE" ]; then
rm -f "$LOGFILE"
fi
exit 0
fi
================================================
FILE: .github/workflows/after-release.yml
================================================
name: After Release
on:
release:
types: [published]
repository_dispatch:
types: [after-release]
workflow_dispatch:
jobs:
# Websocket releases update socketio dep in same repo (manual releases only).
# During weekly releases, the intra-wave hook in release-plan.yml handles this.
self-update:
if: >-
github.event_name == 'release' &&
github.actor != 'github-actions[bot]' &&
contains(github.event.release.tag_name, 'websocket')
uses: gofiber/.github/.github/workflows/after-release.yml@main
with:
skip-wait: ${{ github.event_name == 'workflow_dispatch' }}
repos: |
- gofiber/contrib
secrets:
dispatch-token: ${{ secrets.DISPATCH_TOKEN }}
# Notify downstream repos. Runs for manual releases and weekly batch dispatch.
notify-dependents:
if: github.event_name != 'release' || github.actor != 'github-actions[bot]'
uses: gofiber/.github/.github/workflows/after-release.yml@main
with:
skip-wait: ${{ github.event_name == 'workflow_dispatch' }}
repos: |
- gofiber/recipes
secrets:
dispatch-token: ${{ secrets.DISPATCH_TOKEN }}
================================================
FILE: .github/workflows/auto-labeler.yml
================================================
name: auto-labeler
on:
issues:
types: [opened, edited, milestoned]
pull_request_target:
types: [opened, edited, reopened, synchronize]
workflow_dispatch:
jobs:
auto-labeler:
uses: gofiber/.github/.github/workflows/auto-labeler.yml@main
secrets:
github-token: ${{ secrets.ISSUE_PR_TOKEN }}
with:
config-path: .github/labeler.yml
config-repository: gofiber/.github
================================================
FILE: .github/workflows/cleanup-release-draft.yml
================================================
name: Cleanup Release Draft
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag or name of the draft (empty = latest draft)'
required: false
type: string
default: ''
jobs:
cleanup:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: gofiber/.github/.github/actions/cleanup-release-draft@main
with:
tag: ${{ inputs.tag }}
================================================
FILE: .github/workflows/dependabot-on-demand.yml
================================================
name: Dependabot On-Demand
on:
repository_dispatch:
types: [trigger-dependabot]
workflow_dispatch:
jobs:
trigger:
uses: gofiber/.github/.github/workflows/dependabot-on-demand.yml@main
secrets:
push-token: ${{ secrets.PR_TOKEN }}
================================================
FILE: .github/workflows/dependabot_automerge.yml
================================================
name: Dependabot auto-merge
on:
workflow_dispatch:
pull_request_target:
permissions:
contents: write
pull-requests: write
jobs:
wait_for_checks:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Wait for check is finished
id: wait_for_checks
uses: poseidon/wait-for-status-checks@v0.6.0
with:
token: ${{ secrets.PR_TOKEN }}
match_pattern: Tests
interval: 10
timeout: 600
dependabot:
needs: [wait_for_checks]
name: Dependabot auto-merge
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v3.1.0
with:
github-token: "${{ secrets.PR_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-minor' || steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
run: |
gh pr review --approve "$PR_URL"
gh pr merge --auto --merge "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.PR_TOKEN}}
================================================
FILE: .github/workflows/lint.yml
================================================
name: Linter
on:
push:
branches:
- master
- main
paths-ignore:
- "**/*.md"
- LICENSE
- ".github/ISSUE_TEMPLATE/*.yml"
- ".github/dependabot.yml"
pull_request:
paths-ignore:
- "**/*.md"
- LICENSE
- ".github/ISSUE_TEMPLATE/*.yml"
- ".github/dependabot.yml"
workflow_dispatch:
permissions:
contents: read
pull-requests: read
checks: write
jobs:
lint:
uses: gofiber/.github/.github/workflows/go-lint.yml@main
with:
repo-type: multi
module-dir: v3
golangci-lint-args: "--tests=false --timeout=5m"
================================================
FILE: .github/workflows/release-drafter.yml
================================================
name: Release Drafter (v3 packages)
on:
push:
branches:
- master
- main
workflow_dispatch:
permissions:
contents: read
jobs:
changes:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
outputs:
packages: ${{ github.event_name == 'workflow_dispatch' && steps.filter-setup.outputs.packages || steps.map-packages.outputs.packages || '[]' }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Generate filters
id: filter-setup
shell: bash
run: |
shopt -s nullglob
packages=(v3/*/)
package_names=()
if (( ${#packages[@]} == 0 )); then
echo "filters={}" >> "$GITHUB_OUTPUT"
echo "packages=[]" >> "$GITHUB_OUTPUT"
exit 0
fi
{
echo "filters<<EOF"
for pkg in "${packages[@]}"; do
name=${pkg#v3/}
name=${name%/}
path=${pkg%/}
printf "'%s': '%s/**'\n" "$name" "$path"
package_names+=("$path")
done
echo "EOF"
} >> "$GITHUB_OUTPUT"
if (( ${#package_names[@]} > 0 )); then
printf -v joined '"%s",' "${package_names[@]}"
joined=${joined%,}
echo "packages=[${joined}]" >> "$GITHUB_OUTPUT"
else
echo "packages=[]" >> "$GITHUB_OUTPUT"
fi
- name: Filter changes
id: filter
uses: dorny/paths-filter@v4
if: github.event_name != 'workflow_dispatch'
with:
filters: ${{ steps.filter-setup.outputs.filters }}
- name: Map package paths
id: map-packages
if: github.event_name != 'workflow_dispatch'
env:
FILTER_PACKAGES: ${{ steps.filter.outputs.changes || '[]' }}
run: |
python3 - <<'PY' >> "$GITHUB_OUTPUT"
import json
import os
packages = json.loads(os.environ["FILTER_PACKAGES"])
paths = [f"v3/{name}" for name in packages]
print(f"packages={json.dumps(paths)}")
PY
release-drafter:
needs: changes
runs-on: ubuntu-latest
timeout-minutes: 30
if: needs.changes.outputs.packages != '[]'
permissions:
contents: write
pull-requests: read
strategy:
matrix:
package: ${{ fromJSON(needs.changes.outputs.packages || '[]') }}
steps:
- name: Checkout shared config
uses: actions/checkout@v6
with:
repository: gofiber/.github
sparse-checkout: .github/release-drafter-module.yml
sparse-checkout-cone-mode: false
- name: Generate config from shared template
run: |
sed "s|{{MODULE}}|${{ matrix.package }}|g" \
.github/release-drafter-module.yml > .github/release-drafter-parsed.yml
- name: Run release drafter
uses: release-drafter/release-drafter@v7
with:
config-name: file:release-drafter-parsed.yml
================================================
FILE: .github/workflows/sync-docs.yml
================================================
name: Sync docs
on:
push:
branches: [main, master]
paths: ['**/*.md']
release:
types: [published]
repository_dispatch:
types: [sync-docs]
workflow_dispatch:
inputs:
mode:
description: 'push = sync current docs, release-all = version snapshots for all latest module releases'
type: choice
options: [push, release-all]
default: push
jobs:
sync:
uses: gofiber/.github/.github/workflows/sync-docs.yml@main
with:
repo-type: multi
source-dir: v3
destination-dir: docs/contrib
version-file: contrib_versions.json
docusaurus-command: npm run docusaurus -- docs:version:contrib
commit-url: https://github.com/gofiber/contrib
event-mode: >-
${{ github.event_name == 'workflow_dispatch' && inputs.mode
|| github.event_name == 'repository_dispatch' && 'release-all'
|| github.event_name }}
tag-name: ${{ github.ref_name }}
secrets:
doc-sync-token: ${{ secrets.DOC_SYNC_TOKEN }}
================================================
FILE: .github/workflows/test-casbin.yml
================================================
name: "Test casbin"
on:
push:
branches:
- master
- main
paths:
- 'v3/casbin/**/*.go'
- 'v3/casbin/go.mod'
pull_request:
paths:
- 'v3/casbin/**/*.go'
- 'v3/casbin/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/casbin
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-circuitbreaker.yml
================================================
name: "Test CircuitBreaker"
on:
push:
branches:
- master
- main
paths:
- 'v3/circuitbreaker/**/*.go'
- 'v3/circuitbreaker/go.mod'
pull_request:
paths:
- 'v3/circuitbreaker/**/*.go'
- 'v3/circuitbreaker/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/circuitbreaker
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-coraza.yml
================================================
name: "Test Coraza"
on:
push:
branches:
- master
- main
paths:
- 'v3/coraza/**/*.go'
- 'v3/coraza/go.mod'
pull_request:
paths:
- 'v3/coraza/**/*.go'
- 'v3/coraza/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.26.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/coraza
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-fgprof.yml
================================================
name: "Test Fgprof"
on:
push:
branches:
- master
- main
paths:
- 'v3/fgprof/**/*.go'
- 'v3/fgprof/go.mod'
pull_request:
paths:
- 'v3/fgprof/**/*.go'
- 'v3/fgprof/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/fgprof
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-hcaptcha.yml
================================================
name: "Test hcaptcha"
on:
push:
branches:
- master
- main
paths:
- 'v3/hcaptcha/**/*.go'
- 'v3/hcaptcha/go.mod'
pull_request:
paths:
- 'v3/hcaptcha/**/*.go'
- 'v3/hcaptcha/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/hcaptcha
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-i18n.yml
================================================
name: "Test i18n"
on:
push:
branches:
- master
- main
paths:
- 'v3/i18n/**/*.go'
- 'v3/i18n/go.mod'
pull_request:
paths:
- 'v3/i18n/**/*.go'
- 'v3/i18n/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
- 1.26.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/i18n
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-jwt.yml
================================================
name: "Test jwt"
on:
push:
branches:
- master
- main
paths:
- 'v3/jwt/**/*.go'
- 'v3/jwt/go.mod'
pull_request:
paths:
- 'v3/jwt/**/*.go'
- 'v3/jwt/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/jwt
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-loadshed.yml
================================================
name: "Test Loadshed"
on:
push:
branches:
- master
- main
paths:
- 'v3/loadshed/**/*.go'
- 'v3/loadshed/go.mod'
pull_request:
paths:
- 'v3/loadshed/**/*.go'
- 'v3/loadshed/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
- 1.26.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: "${{ matrix.go-version }}"
- name: Run Test
working-directory: ./v3/loadshed
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-monitor.yml
================================================
name: "Test Monitor"
on:
push:
branches:
- master
- main
paths:
- 'v3/monitor/**/*.go'
- 'v3/monitor/go.mod'
pull_request:
paths:
- 'v3/monitor/**/*.go'
- 'v3/monitor/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/monitor
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-newrelic.yml
================================================
name: "Test newrelic"
on:
push:
branches:
- master
- main
paths:
- 'v3/newrelic/**/*.go'
- 'v3/newrelic/go.mod'
pull_request:
paths:
- 'v3/newrelic/**/*.go'
- 'v3/newrelic/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/newrelic
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-opa.yml
================================================
name: "Test opa"
on:
push:
branches:
- master
- main
paths:
- 'v3/opa/**/*.go'
- 'v3/opa/go.mod'
pull_request:
paths:
- 'v3/opa/**/*.go'
- 'v3/opa/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/opa
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-otel.yml
================================================
name: "Test otel"
on:
push:
branches:
- master
- main
paths:
- 'v3/otel/**/*.go'
- 'v3/otel/go.mod'
pull_request:
paths:
- 'v3/otel/**/*.go'
- 'v3/otel/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
- 1.26.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/otel
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-paseto.yml
================================================
name: "Test paseto"
on:
push:
branches:
- master
- main
paths:
- 'v3/paseto/**/*.go'
- 'v3/paseto/go.mod'
pull_request:
paths:
- 'v3/paseto/**/*.go'
- 'v3/paseto/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/paseto
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-sentry.yml
================================================
name: "Test sentry"
on:
push:
branches:
- master
- main
paths:
- 'v3/sentry/**/*.go'
- 'v3/sentry/go.mod'
pull_request:
paths:
- 'v3/sentry/**/*.go'
- 'v3/sentry/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/sentry
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-socketio.yml
================================================
name: "Test Socket.io"
on:
push:
branches:
- master
- main
paths:
- 'v3/socketio/**/*.go'
- 'v3/socketio/go.mod'
pull_request:
paths:
- 'v3/socketio/**/*.go'
- 'v3/socketio/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: "${{ matrix.go-version }}"
- name: Run Test
working-directory: ./v3/socketio
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-swaggerui.yml
================================================
name: "Test swaggerui"
on:
push:
branches:
- master
- main
paths:
- 'v3/swaggerui/**/*.go'
- 'v3/swaggerui/go.mod'
- 'v3/swaggerui/go.sum'
pull_request:
paths:
- 'v3/swaggerui/**/*.go'
- 'v3/swaggerui/go.mod'
- 'v3/swaggerui/go.sum'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/swaggerui
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-swaggo.yml
================================================
name: "Test swaggo"
on:
push:
branches:
- master
- main
paths:
- 'v3/swaggo/**/*.go'
- 'v3/swaggo/go.mod'
- 'v3/swaggo/go.sum'
pull_request:
paths:
- 'v3/swaggo/**/*.go'
- 'v3/swaggo/go.mod'
- 'v3/swaggo/go.sum'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/swaggo
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-testcontainers.yml
================================================
name: "Test Testcontainers Services"
on:
push:
branches:
- master
- main
paths:
- 'v3/testcontainers/**/*.go'
- 'v3/testcontainers/go.mod'
pull_request:
paths:
- 'v3/testcontainers/**/*.go'
- 'v3/testcontainers/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/testcontainers
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-websocket.yml
================================================
name: "Test websocket"
on:
push:
branches:
- master
- main
paths:
- 'v3/websocket/**/*.go'
- 'v3/websocket/go.mod'
pull_request:
paths:
- 'v3/websocket/**/*.go'
- 'v3/websocket/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
- 1.26.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/websocket
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-zap.yml
================================================
name: "Test zap"
on:
push:
branches:
- master
- main
paths:
- 'v3/zap/**/*.go'
- 'v3/zap/go.mod'
pull_request:
paths:
- 'v3/zap/**/*.go'
- 'v3/zap/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/zap
run: go test -v -race ./...
================================================
FILE: .github/workflows/test-zerolog.yml
================================================
name: "Test zerolog"
on:
push:
branches:
- master
- main
paths:
- 'v3/zerolog/**/*.go'
- 'v3/zerolog/go.mod'
pull_request:
paths:
- 'v3/zerolog/**/*.go'
- 'v3/zerolog/go.mod'
workflow_dispatch:
jobs:
Tests:
runs-on: ubuntu-latest
env:
GOWORK: off
strategy:
matrix:
go-version:
- 1.25.x
steps:
- name: Fetch Repository
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '${{ matrix.go-version }}'
- name: Run Test
working-directory: ./v3/zerolog
run: go test -v -race ./...
================================================
FILE: .github/workflows/weekly-release.yml
================================================
name: Weekly release
on:
schedule:
- cron: '0 8 * * 4'
workflow_dispatch:
inputs:
draft-tags:
description: >
Tags or package names to publish, comma-separated (e.g.
"websocket,jwt" or "v3/websocket/v1.2.0"). Matched as
substrings. Empty = all.
type: string
default: ''
dry-run:
description: Show diff but do not publish.
type: boolean
default: false
delay:
description: Minutes between module publishes (default 2).
type: string
default: '2'
concurrency:
group: weekly-release-${{ github.repository }}
cancel-in-progress: false
permissions:
contents: write
pull-requests: read
issues: write
jobs:
release:
uses: gofiber/.github/.github/workflows/weekly-release.yml@main
with:
repo-type: multi
dry-run: ${{ inputs.dry-run || false }}
draft-tags: ${{ inputs.draft-tags || '' }}
publish-delay-minutes: ${{ inputs.delay || '2' }}
secrets:
dispatch-token: ${{ secrets.DISPATCH_TOKEN }}
================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
*.tmp
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# IDE files
.vscode
.DS_Store
.idea
# Misc
*.fiber.gz
*.fasthttp.gz
*.pprof
*.workspace
# Dependencies
vendor
/Godeps/
# Go workspace file
go.work.sum
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2021 Fiber
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
---
title: 👋 Welcome
sidebar_position: 1
---
> 📦 The canonical copy of this README lives in [v3/README.md](./v3/README.md).
<div align="center">
<img height="125" alt="Fiber" src="https://raw.githubusercontent.com/gofiber/contrib/master/.github/logo-dark.svg#gh-dark-mode-only" />
<img height="125" alt="Fiber" src="https://raw.githubusercontent.com/gofiber/contrib/master/.github/logo.svg#gh-light-mode-only" />
<br />
[](https://gofiber.io/discord)

Repository for third party middlewares and service implementations, with dependencies.
> **Go version support:** We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information.
</div>
## 📑 Middleware Implementations
* [casbin](./v3/casbin/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Casbin%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-casbin.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="casbin workflow status" /> </a>
* [circuitbreaker](./v3/circuitbreaker/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+CircuitBreaker%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-circuitbreaker.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="circuitbreaker workflow status" /> </a>
* [fgprof](./v3/fgprof/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Fgprof%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-fgprof.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="fgprof workflow status" /> </a>
* [i18n](./v3/i18n/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+i18n%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-i18n.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="i18n workflow status" /> </a>
* [sentry](./v3/sentry/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+sentry%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-sentry.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="sentry workflow status" /> </a>
* [zap](./v3/zap/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+zap%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-zap.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="zap workflow status" /> </a>
* [zerolog](./v3/zerolog/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+zerolog%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-zerolog.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="zerolog workflow status" /> </a>
* [hcaptcha](./v3/hcaptcha/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+hcaptcha%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-hcaptcha.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="hcaptcha workflow status" /> </a>
* [jwt](./v3/jwt/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+jwt%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-jwt.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="jwt workflow status" /> </a>
* [loadshed](./v3/loadshed/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+loadshed%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-loadshed.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="loadshed workflow status" /> </a>
* [new relic](./v3/newrelic/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+newrelic%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-newrelic.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="new relic workflow status" /> </a>
* [monitor](./v3/monitor/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Monitor%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-monitor.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="monitor workflow status" /> </a>
* [open policy agent](./v3/opa/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+opa%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-opa.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="OPA workflow status" /> </a>
* [otel (opentelemetry)](./v3/otel/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+otel%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-otel.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="otel workflow status" /> </a>
* [paseto](./v3/paseto/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+paseto%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-paseto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="paseto workflow status" /> </a>
* [socket.io](./v3/socketio/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+socketio%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-socketio.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="socket.io workflow status" /> </a>
* [swaggo](./v3/swaggo/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+swaggo%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swaggo.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="swaggo workflow status" /> </a> _(formerly `gofiber/swagger`)_
* [swaggerui](./v3/swaggerui/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+swaggerui%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swaggerui.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="swaggerui workflow status" /> </a> _(formerly `gofiber/contrib/swagger`)_
* [websocket](./v3/websocket/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+websocket%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-websocket.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="websocket workflow status" /> </a>
## 🥡 Service Implementations
* [testcontainers](./v3/testcontainers/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Testcontainers%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-testcontainers.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="testcontainers workflow status" /> </a>
================================================
FILE: go.work
================================================
go 1.26.1
use (
./v3/casbin
./v3/circuitbreaker
./v3/coraza
./v3/fgprof
./v3/hcaptcha
./v3/i18n
./v3/jwt
./v3/loadshed
./v3/monitor
./v3/newrelic
./v3/opa
./v3/otel
./v3/paseto
./v3/sentry
./v3/socketio
./v3/swaggerui
./v3/swaggo
./v3/testcontainers
./v3/websocket
./v3/zap
./v3/zerolog
)
================================================
FILE: v3/.golangci.yml
================================================
version: "2"
run:
go: "1.25"
timeout: 5m
tests: false
================================================
FILE: v3/README.md
================================================
---
title: 👋 Welcome
sidebar_position: 1
---
<div align="center">
<img height="125" alt="Fiber" src="https://raw.githubusercontent.com/gofiber/contrib/master/.github/logo-dark.svg#gh-dark-mode-only" />
<img height="125" alt="Fiber" src="https://raw.githubusercontent.com/gofiber/contrib/master/.github/logo.svg#gh-light-mode-only" />
<br />
[](https://gofiber.io/discord)

Repository for third party middlewares and service implementations, with dependencies.
> **Go version support:** We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information.
</div>
## 📑 Middleware Implementations
* [casbin](./casbin/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Casbin%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-casbin.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="casbin workflow status" /> </a>
* [circuitbreaker](./circuitbreaker/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+CircuitBreaker%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-circuitbreaker.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="circuitbreaker workflow status" /> </a>
* [coraza](./coraza/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Coraza%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-coraza.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="coraza workflow status" /> </a>
* [fgprof](./fgprof/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Fgprof%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-fgprof.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="fgprof workflow status" /> </a>
* [i18n](./i18n/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+i18n%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-i18n.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="i18n workflow status" /> </a>
* [sentry](./sentry/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+sentry%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-sentry.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="sentry workflow status" /> </a>
* [zap](./zap/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+zap%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-zap.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="zap workflow status" /> </a>
* [zerolog](./zerolog/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+zerolog%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-zerolog.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="zerolog workflow status" /> </a>
* [hcaptcha](./hcaptcha/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+hcaptcha%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-hcaptcha.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="hcaptcha workflow status" /> </a>
* [jwt](./jwt/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+jwt%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-jwt.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="jwt workflow status" /> </a>
* [loadshed](./loadshed/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+loadshed%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-loadshed.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="loadshed workflow status" /> </a>
* [new relic](./newrelic/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+newrelic%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-newrelic.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="new relic workflow status" /> </a>
* [monitor](./monitor/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Monitor%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-monitor.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="monitor workflow status" /> </a>
* [open policy agent](./opa/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+opa%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-opa.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="OPA workflow status" /> </a>
* [otel (opentelemetry)](./otel/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+otel%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-otel.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="otel workflow status" /> </a>
* [paseto](./paseto/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+paseto%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-paseto.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="paseto workflow status" /> </a>
* [socket.io](./socketio/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+socketio%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-socketio.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="socket.io workflow status" /> </a>
* [swaggo](./swaggo/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+swaggo%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swaggo.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="swaggo workflow status" /> </a> _(formerly `gofiber/swagger`)_
* [swaggerui](./swaggerui/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+swaggerui%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-swaggerui.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="swaggerui workflow status" /> </a> _(formerly `gofiber/contrib/swagger`)_
* [websocket](./websocket/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+websocket%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-websocket.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="websocket workflow status" /> </a>
## 🥡 Service Implementations
* [testcontainers](./testcontainers/README.md) <a href="https://github.com/gofiber/contrib/actions?query=workflow%3A%22Test+Testcontainers%22"> <img src="https://img.shields.io/github/actions/workflow/status/gofiber/contrib/test-testcontainers.yml?branch=main&label=%F0%9F%A7%AA%20&style=flat&color=75C46B" alt="testcontainers workflow status" /> </a>
================================================
FILE: v3/casbin/README.md
================================================
---
id: casbin
---
# Casbin

[](https://gofiber.io/discord)

Casbin middleware for Fiber.
**Compatible with Fiber v3.**
## Go version support
We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information.
## Install
```sh
go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/contrib/v3/casbin
```
choose an adapter from [here](https://casbin.org/docs/adapters)
```sh
go get -u github.com/casbin/xorm-adapter
```
## Signature
```go
casbin.New(config ...casbin.Config) *casbin.Middleware
```
## Config
| Property | Type | Description | Default |
|:--------------|:--------------------------|:-----------------------------------------|:--------------------------------------------------------------|
| ModelFilePath | `string` | Model file path | `"./model.conf"` |
| PolicyAdapter | `persist.Adapter` | Database adapter for policies | `./policy.csv` |
| Enforcer | `*casbin.Enforcer` | Custom casbin enforcer | `Middleware generated enforcer using ModelFilePath & PolicyAdapter` |
| Lookup | `func(fiber.Ctx) string` | Look up for current subject | `""` |
| Unauthorized | `func(fiber.Ctx) error` | Response body for unauthorized responses | `Unauthorized` |
| Forbidden | `func(fiber.Ctx) error` | Response body for forbidden responses | `Forbidden` |
### Examples
- [Gorm Adapter](https://github.com/svcg/-fiber_casbin_demo)
- [File Adapter](https://github.com/gofiber/contrib/tree/master/v3/casbin/example)
## CustomPermission
```go
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/contrib/v3/casbin"
_ "github.com/go-sql-driver/mysql"
"github.com/casbin/xorm-adapter/v2"
)
func main() {
app := fiber.New()
authz := casbin.New(casbin.Config{
ModelFilePath: "path/to/rbac_model.conf",
PolicyAdapter: xormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/"),
Lookup: func(c fiber.Ctx) string {
// fetch authenticated user subject
},
})
app.Post("/blog",
authz.RequiresPermissions([]string{"blog:create"}, casbin.WithValidationRule(casbin.MatchAllRule)),
func(c fiber.Ctx) error {
// your handler
},
)
app.Delete("/blog/:id",
authz.RequiresPermissions([]string{"blog:create", "blog:delete"}, casbin.WithValidationRule(casbin.AtLeastOneRule)),
func(c fiber.Ctx) error {
// your handler
},
)
app.Listen(":8080")
}
```
## RoutePermission
```go
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/contrib/v3/casbin"
_ "github.com/go-sql-driver/mysql"
"github.com/casbin/xorm-adapter/v2"
)
func main() {
app := fiber.New()
authz := casbin.New(casbin.Config{
ModelFilePath: "path/to/rbac_model.conf",
PolicyAdapter: xormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/"),
Lookup: func(c fiber.Ctx) string {
// fetch authenticated user subject
},
})
// check permission with Method and Path
app.Post("/blog",
authz.RoutePermission(),
func(c fiber.Ctx) error {
// your handler
},
)
app.Listen(":8080")
}
```
## RoleAuthorization
```go
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/contrib/v3/casbin"
_ "github.com/go-sql-driver/mysql"
"github.com/casbin/xorm-adapter/v2"
)
func main() {
app := fiber.New()
authz := casbin.New(casbin.Config{
ModelFilePath: "path/to/rbac_model.conf",
PolicyAdapter: xormadapter.NewAdapter("mysql", "root:@tcp(127.0.0.1:3306)/"),
Lookup: func(c fiber.Ctx) string {
// fetch authenticated user subject
},
})
app.Put("/blog/:id",
authz.RequiresRoles([]string{"admin"}),
func(c fiber.Ctx) error {
// your handler
},
)
app.Listen(":8080")
}
```
================================================
FILE: v3/casbin/casbin.go
================================================
package casbin
import (
"fmt"
"github.com/gofiber/fiber/v3"
)
// Middleware ...
type Middleware struct {
config Config
}
// New creates an authorization middleware for use in Fiber
func New(config ...Config) *Middleware {
cfg, err := configDefault(config...)
if err != nil {
panic(fmt.Errorf("fiber: casbin middleware error -> %w", err))
}
return &Middleware{
config: cfg,
}
}
// RequiresPermissions tries to find the current subject and determine if the
// subject has the required permissions according to predefined Casbin policies.
func (m *Middleware) RequiresPermissions(permissions []string, opts ...Option) fiber.Handler {
options := optionsDefault(opts...)
return func(c fiber.Ctx) error {
if len(permissions) == 0 {
return c.Next()
}
sub := m.config.Lookup(c)
if len(sub) == 0 {
return m.config.Unauthorized(c)
}
switch options.ValidationRule {
case MatchAllRule:
for _, permission := range permissions {
vals := append([]string{sub}, options.PermissionParser(permission)...)
if ok, err := m.config.Enforcer.Enforce(stringSliceToInterfaceSlice(vals)...); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
} else if !ok {
return m.config.Forbidden(c)
}
}
return c.Next()
case AtLeastOneRule:
for _, permission := range permissions {
vals := append([]string{sub}, options.PermissionParser(permission)...)
if ok, err := m.config.Enforcer.Enforce(stringSliceToInterfaceSlice(vals)...); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
} else if ok {
return c.Next()
}
}
return m.config.Forbidden(c)
}
return c.Next()
}
}
// RoutePermission tries to find the current subject and determine if the
// subject has the required permissions according to predefined Casbin policies.
// This method uses http Path and Method as object and action.
func (m *Middleware) RoutePermission() fiber.Handler {
return func(c fiber.Ctx) error {
sub := m.config.Lookup(c)
if len(sub) == 0 {
return m.config.Unauthorized(c)
}
if ok, err := m.config.Enforcer.Enforce(sub, c.Path(), c.Method()); err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
} else if !ok {
return m.config.Forbidden(c)
}
return c.Next()
}
}
// RequiresRoles tries to find the current subject and determine if the
// subject has the required roles according to predefined Casbin policies.
func (m *Middleware) RequiresRoles(roles []string, opts ...Option) fiber.Handler {
options := optionsDefault(opts...)
return func(c fiber.Ctx) error {
if len(roles) == 0 {
return c.Next()
}
sub := m.config.Lookup(c)
if len(sub) == 0 {
return m.config.Unauthorized(c)
}
userRoles, err := m.config.Enforcer.GetRolesForUser(sub)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
switch options.ValidationRule {
case MatchAllRule:
for _, role := range roles {
if !containsString(userRoles, role) {
return m.config.Forbidden(c)
}
}
return c.Next()
case AtLeastOneRule:
for _, role := range roles {
if containsString(userRoles, role) {
return c.Next()
}
}
return m.config.Forbidden(c)
}
return c.Next()
}
}
================================================
FILE: v3/casbin/casbin_test.go
================================================
package casbin
import (
"errors"
"net/http"
"strings"
"testing"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
"github.com/casbin/casbin/v2/persist"
"github.com/gofiber/fiber/v3"
)
var (
subjectAlice = func(c fiber.Ctx) string { return "alice" }
subjectBob = func(c fiber.Ctx) string { return "bob" }
subjectEmpty = func(c fiber.Ctx) string { return "" }
)
const (
modelConf = `
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`
policyList = `
p,admin,blog,create
p,admin,blog,update
p,admin,blog,delete
p,user,comment,create
p,user,comment,delete
p,admin,/blog,POST
p,admin,/blog/1,PUT
p,admin,/blog/1,DELETE
p,user,/comment,POST
g,alice,admin
g,alice,user
g,bob,user`
)
// mockAdapter
type mockAdapter struct {
text string
}
func newMockAdapter(text string) *mockAdapter {
return &mockAdapter{
text: text,
}
}
func (ma *mockAdapter) LoadPolicy(model model.Model) error {
if ma.text == "" {
return errors.New("text is required")
}
strs := strings.Split(ma.text, "\n")
for _, str := range strs {
if str == "" {
continue
}
persist.LoadPolicyLine(str, model)
}
return nil
}
func (ma *mockAdapter) SavePolicy(model model.Model) error {
return errors.New("not implemented")
}
func (ma *mockAdapter) AddPolicy(sec string, ptype string, rule []string) error {
return errors.New("not implemented")
}
func (ma *mockAdapter) RemovePolicy(sec string, ptype string, rule []string) error {
return errors.New("not implemented")
}
func (ma *mockAdapter) RemoveFilteredPolicy(sec string, ptype string, fieldIndex int, fieldValues ...string) error {
return errors.New("not implemented")
}
func setup() (*casbin.Enforcer, error) {
m, err := model.NewModelFromString(modelConf)
if err != nil {
return nil, err
}
enf, err := casbin.NewEnforcer(m, newMockAdapter(policyList))
if err != nil {
return nil, err
}
return enf, nil
}
func Test_RequiresPermission(t *testing.T) {
enf, err := setup()
if err != nil {
t.Fatal(err)
}
testCases := []struct {
desc string
lookup func(fiber.Ctx) string
permissions []string
opts []Option
statusCode int
}{
{
desc: "alice has permission to create blog",
lookup: subjectAlice,
permissions: []string{"blog:create"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 200,
},
{
desc: "alice has permission to create blog",
lookup: subjectAlice,
permissions: []string{"blog:create"},
opts: []Option{WithValidationRule(AtLeastOneRule)},
statusCode: 200,
},
{
desc: "alice has permission to create and update blog",
lookup: subjectAlice,
permissions: []string{"blog:create", "blog:update"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 200,
},
{
desc: "alice has permission to create comment or blog",
lookup: subjectAlice,
permissions: []string{"comment:create", "blog:create"},
opts: []Option{WithValidationRule(AtLeastOneRule)},
statusCode: 200,
},
{
desc: "bob has only permission to create comment",
lookup: subjectBob,
permissions: []string{"comment:create", "blog:create"},
opts: []Option{WithValidationRule(AtLeastOneRule)},
statusCode: 200,
},
{
desc: "unauthenticated user has no permissions",
lookup: subjectEmpty,
permissions: []string{"comment:create"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 401,
},
{
desc: "bob has not permission to create blog",
lookup: subjectBob,
permissions: []string{"blog:create"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 403,
},
{
desc: "bob has not permission to delete blog",
lookup: subjectBob,
permissions: []string{"blog:delete"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 403,
},
{
desc: "invalid permission",
lookup: subjectBob,
permissions: []string{"unknown"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 500,
},
}
for _, tC := range testCases {
app := fiber.New()
authz := New(Config{
Enforcer: enf,
Lookup: tC.lookup,
})
app.Post("/blog",
authz.RequiresPermissions(tC.permissions, tC.opts...),
func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
},
)
t.Run(tC.desc, func(t *testing.T) {
req, err := http.NewRequest("POST", "/blog", nil)
if err != nil {
t.Fatal(err)
}
req.Host = "localhost"
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != tC.statusCode {
t.Errorf(`StatusCode: got %v - expected %v`, resp.StatusCode, tC.statusCode)
}
})
}
}
func Test_RequiresRoles(t *testing.T) {
enf, err := setup()
if err != nil {
t.Fatal(err)
}
testCases := []struct {
desc string
lookup func(fiber.Ctx) string
roles []string
opts []Option
statusCode int
}{
{
desc: "alice has user role",
lookup: subjectAlice,
roles: []string{"user"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 200,
},
{
desc: "alice has admin role",
lookup: subjectAlice,
roles: []string{"admin"},
opts: []Option{WithValidationRule(AtLeastOneRule)},
statusCode: 200,
},
{
desc: "alice has both user and admin roles",
lookup: subjectAlice,
roles: []string{"user", "admin"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 200,
},
{
desc: "alice has both user and admin roles",
lookup: subjectAlice,
roles: []string{"user", "admin"},
opts: []Option{WithValidationRule(AtLeastOneRule)},
statusCode: 200,
},
{
desc: "bob has only user role",
lookup: subjectBob,
roles: []string{"user"},
opts: []Option{WithValidationRule(AtLeastOneRule)},
statusCode: 200,
},
{
desc: "unauthenticated user has no permissions",
lookup: subjectEmpty,
roles: []string{"user"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 401,
},
{
desc: "bob has not admin role",
lookup: subjectBob,
roles: []string{"admin"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 403,
},
{
desc: "bob has only user role",
lookup: subjectBob,
roles: []string{"admin", "user"},
opts: []Option{WithValidationRule(AtLeastOneRule)},
statusCode: 200,
},
{
desc: "invalid role",
lookup: subjectBob,
roles: []string{"unknown"},
opts: []Option{WithValidationRule(MatchAllRule)},
statusCode: 403,
},
}
for _, tC := range testCases {
app := fiber.New()
authz := New(Config{
Enforcer: enf,
Lookup: tC.lookup,
})
app.Post("/blog",
authz.RequiresRoles(tC.roles, tC.opts...),
func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
},
)
t.Run(tC.desc, func(t *testing.T) {
req, err := http.NewRequest("POST", "/blog", nil)
if err != nil {
t.Fatal(err)
}
req.Host = "localhost"
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != tC.statusCode {
t.Errorf(`StatusCode: got %v - expected %v`, resp.StatusCode, tC.statusCode)
}
})
}
}
func Test_RoutePermission(t *testing.T) {
enf, err := setup()
if err != nil {
t.Fatal(err)
}
testCases := []struct {
desc string
url string
method string
subject string
statusCode int
}{
{
desc: "alice has permission to create blog",
url: "/blog",
method: "POST",
subject: "alice",
statusCode: 200,
},
{
desc: "alice has permission to update blog",
url: "/blog/1",
method: "PUT",
subject: "alice",
statusCode: 200,
},
{
desc: "bob has only permission to create comment",
url: "/comment",
method: "POST",
subject: "bob",
statusCode: 200,
},
{
desc: "unauthenticated user has no permissions",
url: "/",
method: "POST",
subject: "",
statusCode: 401,
},
{
desc: "bob has not permission to create blog",
url: "/blog",
method: "POST",
subject: "bob",
statusCode: 403,
},
{
desc: "bob has not permission to delete blog",
url: "/blog/1",
method: "DELETE",
subject: "bob",
statusCode: 403,
},
}
app := fiber.New()
authz := New(Config{
Enforcer: enf,
Lookup: func(c fiber.Ctx) string {
return c.Get("x-subject")
},
})
app.Use(authz.RoutePermission())
app.Post("/blog",
func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
},
)
app.Put("/blog/:id",
func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
},
)
app.Delete("/blog/:id",
func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
},
)
app.Post("/comment",
func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
},
)
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
req, err := http.NewRequest(tC.method, tC.url, nil)
if err != nil {
t.Fatal(err)
}
req.Host = "localhost"
req.Header.Set("x-subject", tC.subject)
resp, err := app.Test(req)
if err != nil {
t.Fatal(err)
}
if resp.StatusCode != tC.statusCode {
t.Errorf(`StatusCode: got %v - expected %v`, resp.StatusCode, tC.statusCode)
}
})
}
}
================================================
FILE: v3/casbin/config.go
================================================
package casbin
import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/persist"
fileadapter "github.com/casbin/casbin/v2/persist/file-adapter"
"github.com/gofiber/fiber/v3"
)
// Config holds the configuration for the middleware
type Config struct {
// ModelFilePath is path to model file for Casbin.
// Optional. Default: "./model.conf".
ModelFilePath string
// PolicyAdapter is an interface for different persistent providers.
// Optional. Default: fileadapter.NewAdapter("./policy.csv").
PolicyAdapter persist.Adapter
// Enforcer is an enforcer. If you want to use your own enforcer.
// Optional. Default: nil
Enforcer *casbin.Enforcer
// Lookup is a function that is used to look up current subject.
// An empty string is considered as unauthenticated user.
// Optional. Default: func(c fiber.Ctx) string { return "" }
Lookup func(fiber.Ctx) string
// Unauthorized defines the response body for unauthorized responses.
// Optional. Default: func(c fiber.Ctx) error { return c.SendStatus(401) }
Unauthorized fiber.Handler
// Forbidden defines the response body for forbidden responses.
// Optional. Default: func(c fiber.Ctx) error { return c.SendStatus(403) }
Forbidden fiber.Handler
}
var ConfigDefault = Config{
ModelFilePath: "./model.conf",
PolicyAdapter: fileadapter.NewAdapter("./policy.csv"),
Lookup: func(c fiber.Ctx) string { return "" },
Unauthorized: func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusUnauthorized) },
Forbidden: func(c fiber.Ctx) error { return c.SendStatus(fiber.StatusForbidden) },
}
// Helper function to set default values
func configDefault(config ...Config) (Config, error) {
// Return default config if nothing provided
if len(config) < 1 {
return ConfigDefault, nil
}
// Override default config
cfg := config[0]
if cfg.Enforcer == nil {
if cfg.ModelFilePath == "" {
cfg.ModelFilePath = ConfigDefault.ModelFilePath
}
if cfg.PolicyAdapter == nil {
cfg.PolicyAdapter = ConfigDefault.PolicyAdapter
}
enforcer, err := casbin.NewEnforcer(cfg.ModelFilePath, cfg.PolicyAdapter)
if err != nil {
return cfg, err
}
cfg.Enforcer = enforcer
}
if cfg.Lookup == nil {
cfg.Lookup = ConfigDefault.Lookup
}
if cfg.Unauthorized == nil {
cfg.Unauthorized = ConfigDefault.Unauthorized
}
if cfg.Forbidden == nil {
cfg.Forbidden = ConfigDefault.Forbidden
}
return cfg, nil
}
================================================
FILE: v3/casbin/go.mod
================================================
module github.com/gofiber/contrib/v3/casbin
go 1.25.0
require (
github.com/casbin/casbin/v2 v2.135.0
github.com/gofiber/fiber/v3 v3.1.0
)
require (
github.com/andybalholm/brotli v1.2.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
github.com/casbin/govaluate v1.10.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gofiber/schema v1.7.1 // indirect
github.com/gofiber/utils/v2 v2.0.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.70.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
)
================================================
FILE: v3/casbin/go.sum
================================================
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk=
github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18=
github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0=
github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
github.com/gofiber/schema v1.7.1 h1:oSJBKdgP8JeIME4TQSAqlNKTU2iBB+2RNmKi8Nsc+TI=
github.com/gofiber/schema v1.7.1/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
github.com/gofiber/utils/v2 v2.0.3 h1:qJyfS/t7s7Z4+/zlU1i1pafYNP2+xLupVPgkW8ce1uI=
github.com/gofiber/utils/v2 v2.0.3/go.mod h1:GGERKU3Vhj5z6hS8YKvxL99A54DjOvTFZ0cjZnG4Lj4=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/shamaton/msgpack/v3 v3.1.0 h1:jsk0vEAqVvvS9+fTZ5/EcQ9tz860c9pWxJ4Iwecz8gU=
github.com/shamaton/msgpack/v3 v3.1.0/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA=
github.com/valyala/fasthttp v1.70.0/go.mod h1:oDZEHHkJ/Buyklg6uURmYs19442zFSnCIfX3j1FY3pE=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: v3/casbin/options.go
================================================
package casbin
import "strings"
const (
MatchAllRule ValidationRule = iota
AtLeastOneRule
)
var OptionsDefault = Options{
ValidationRule: MatchAllRule,
PermissionParser: PermissionParserWithSeperator(":"),
}
type (
ValidationRule int
// PermissionParserFunc is used for parsing the permission
// to extract object and action usually
PermissionParserFunc func(str string) []string
OptionFunc func(*Options)
// Option specifies casbin configuration options.
Option interface {
apply(*Options)
}
// Options holds Options of middleware
Options struct {
ValidationRule ValidationRule
PermissionParser PermissionParserFunc
}
)
func (of OptionFunc) apply(o *Options) {
of(o)
}
func WithValidationRule(vr ValidationRule) Option {
return OptionFunc(func(o *Options) {
o.ValidationRule = vr
})
}
func WithPermissionParser(pp PermissionParserFunc) Option {
return OptionFunc(func(o *Options) {
o.PermissionParser = pp
})
}
func PermissionParserWithSeperator(sep string) PermissionParserFunc {
return func(str string) []string {
return strings.Split(str, sep)
}
}
// Helper function to set default values
func optionsDefault(opts ...Option) Options {
cfg := OptionsDefault
for _, opt := range opts {
opt.apply(&cfg)
}
return cfg
}
================================================
FILE: v3/casbin/utils.go
================================================
package casbin
func containsString(s []string, v string) bool {
for _, vv := range s {
if vv == v {
return true
}
}
return false
}
func stringSliceToInterfaceSlice(s []string) []interface{} {
res := make([]interface{}, len(s))
for i, v := range s {
res[i] = v
}
return res
}
================================================
FILE: v3/circuitbreaker/README.md
================================================
---
id: circuitbreaker
---
# Circuit Breaker

[](https://gofiber.io/discord)

A **Circuit Breaker** is a software design pattern used to prevent system failures when a service is experiencing high failures or slow responses. It helps improve system resilience by **stopping requests** to an unhealthy service and **allowing recovery** once it stabilizes.
**Compatible with Fiber v3.**
## Go version support
We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information.
## How It Works
1. **Closed State:**
- Requests are allowed to pass normally.
- Failures are counted.
- If failures exceed a defined **threshold**, the circuit switches to **Open** state.
2. **Open State:**
- Requests are **blocked immediately** to prevent overload.
- The circuit stays open for a **timeout period** before moving to **Half-Open**.
3. **Half-Open State:**
- Allows a limited number of requests to test service recovery.
- If requests **succeed**, the circuit resets to **Closed**.
- If requests **fail**, the circuit returns to **Open**.
## Benefits of Using a Circuit Breaker
✅ **Prevents cascading failures** in microservices.
✅ **Improves system reliability** by avoiding repeated failed requests.
✅ **Reduces load on struggling services** and allows recovery.
## Install
```bash
go get -u github.com/gofiber/fiber/v3
go get -u github.com/gofiber/contrib/v3/circuitbreaker
```
## Signature
```go
circuitbreaker.New(config ...circuitbreaker.Config) *circuitbreaker.Middleware
```
## Config
| Property | Type | Description | Default |
|:---------|:-----|:------------|:--------|
| FailureThreshold | `int` | Number of consecutive errors required to open the circuit | `5` |
| Timeout | `time.Duration` | Timeout for the circuit breaker | `10 * time.Second` |
| SuccessThreshold | `int` | Number of successful requests required to close the circuit | `5` |
| HalfOpenMaxConcurrent | `int` | Max concurrent requests in half-open state | `1` |
| IsFailure | `func(error) bool` | Custom function to determine if an error is a failure | `Status >= 500` |
| OnOpen | `func(fiber.Ctx) error` | Callback function when the circuit is opened | `503 response` |
| OnClose | `func(fiber.Ctx) error` | Callback function when the circuit is closed | `Continue request` |
| OnHalfOpen | `func(fiber.Ctx) error` | Callback function when the circuit is half-open | `429 response` |
## Circuit Breaker Usage in Fiber (Example)
This guide explains how to use a Circuit Breaker in a Fiber application at different levels, from basic setup to advanced customization.
### 1. Basic Setup
A **global** Circuit Breaker protects all routes.
**Example: Applying Circuit Breaker to All Routes**
```go
package main
import (
"github.com/gofiber/fiber/v3"
"github.com/gofiber/contrib/v3/circuitbreaker"
)
func main() {
app := fiber.New()
// Create a new Circuit Breaker with custom configuration
cb := circuitbreaker.New(circuitbreaker.Config{
FailureThreshold: 3, // Max failures before opening the circuit
Timeout: 5 * time.Second, // Wait time before retrying
SuccessThreshold: 2, // Required successes to move back to closed state
})
// Apply Circuit Breaker to ALL routes
app.Use(circuitbreaker.Middleware(cb))
// Sample Route
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("Hello, world!")
})
// Optional: Expose health check endpoint
app.Get("/health/circuit", cb.HealthHandler())
// Optional: Expose metrics about the circuit breaker:
app.Get("/metrics/circuit", func(c fiber.Ctx) error {
return c.JSON(cb.GetStateStats())
})
app.Listen(":3000")
// In your application shutdown logic
app.Shutdown(func() {
// Make sure to stop the circuit breaker when your application shuts down:
cb.Stop()
})
}
```
### 2. Route & Route-Group Specific Circuit Breaker
Apply the Circuit Breaker **only to specific routes**.
```go
app.Get("/protected", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error {
return c.SendString("Protected service running")
})
```
Apply the Circuit Breaker **only to specific routes groups**.
```go
app := route.Group("/api")
app.Use(circuitbreaker.Middleware(cb))
// All routes in this group will be protected
app.Get("/users", getUsersHandler)
app.Post("/users", createUserHandler)
```
### 3. Circuit Breaker with Custom Failure Handling
Customize the response when the circuit **opens**.
```go
cb := circuitbreaker.New(circuitbreaker.Config{
FailureThreshold: 3,
Timeout: 10 * time.Second,
OnOpen: func(c fiber.Ctx) error {
return c.Status(fiber.StatusServiceUnavailable).
JSON(fiber.Map{"error": "Circuit Open: Service unavailable"})
},
OnHalfOpen: func(c fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).
JSON(fiber.Map{"error": "Circuit Half-Open: Retrying service"})
},
OnClose: func(c fiber.Ctx) error {
return c.Status(fiber.StatusOK).
JSON(fiber.Map{"message": "Circuit Closed: Service recovered"})
},
})
// Apply to a specific route
app.Get("/custom", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error {
return c.SendString("This service is protected by a Circuit Breaker")
})
```
✅ Now, when failures exceed the threshold, ***custom error responses** will be sent.
### 4. Circuit Breaker for External API Calls
Use a Circuit Breaker **when calling an external API.**
```go
app.Get("/external-api", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error {
// Simulating an external API call
resp, err := fiber.Get("https://example.com/api")
if err != nil {
return fiber.NewError(fiber.StatusInternalServerError, "External API failed")
}
return c.SendString(resp.Body())
})
```
✅ If the external API fails repeatedly, **the circuit breaker prevents further calls.**
### 5. Circuit Breaker with Concurrent Requests Handling
Use a **semaphore-based** approach to **limit concurrent requests.**
```go
cb := circuitbreaker.New(circuitbreaker.Config{
FailureThreshold: 3,
Timeout: 5 * time.Second,
SuccessThreshold: 2,
HalfOpenSemaphore: make(chan struct{}, 2), // Allow only 2 concurrent requests
})
app.Get("/half-open-limit", circuitbreaker.Middleware(cb), func(c fiber.Ctx) error {
time.Sleep(2 * time.Second) // Simulating slow response
return c.SendString("Half-Open: Limited concurrent requests")
})
```
✅ When in **half-open** state, only **2 concurrent requests are allowed**.
### 6. Circuit Breaker with Custom Metrics
Integrate **Prometheus metrics** and **structured logging**.
```go
cb := circuitbreaker.New(circuitbreaker.Config{
FailureThreshold: 5,
Timeout: 10 * time.Second,
OnOpen: func(c fiber.Ctx) error {
log.Println("Circuit Breaker Opened!")
prometheus.Inc("circuit_breaker_open_count")
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{"error": "Service Down"})
},
})
```
✅ Logs when the circuit opens & increments Prometheus metrics.
### 7. Advanced: Multiple Circuit Breakers for Different Services
Use different Circuit Breakers for different services.
```go
dbCB := circuitbreaker.New(circuitbreaker.Config{FailureThreshold: 5, Timeout: 10 * time.Second})
apiCB := circuitbreaker.New(circuitbreaker.Config{FailureThreshold: 3, Timeout: 5 * time.Second})
app.Get("/db-service", circuitbreaker.Middleware(dbCB), func(c fiber.Ctx) error {
return c.SendString("DB service request")
})
app.Get("/api-service", circuitbreaker.Middleware(apiCB), func(c fiber.Ctx) error {
return c.SendString("External API service request")
})
```
✅ Each service has its own failure threshold & timeout.
================================================
FILE: v3/circuitbreaker/circuitbreaker.go
================================================
package circuitbreaker
import (
"context"
"net/http"
"sync"
"sync/atomic"
"time"
"github.com/gofiber/fiber/v3"
)
// State represents the state of the circuit breaker
type State string
const (
StateClosed State = "closed" // Normal operation
StateOpen State = "open" // Requests are blocked
StateHalfOpen State = "half-open" // Limited requests allowed to check recovery
)
// Config holds the configurable parameters
type Config struct {
// Failure threshold to trip the circuit
FailureThreshold int
// Duration circuit stays open before allowing test requests
Timeout time.Duration
// Success threshold to close the circuit from half-open
SuccessThreshold int
// Maximum concurrent requests allowed in half-open state
HalfOpenMaxConcurrent int
// Custom failure detector function (return true if response should count as failure)
IsFailure func(c fiber.Ctx, err error) bool
// Callbacks for state transitions
OnOpen func(fiber.Ctx) error // Called when circuit opens
OnHalfOpen func(fiber.Ctx) error // Called when circuit transitions to half-open
OnClose func(fiber.Ctx) error // Called when circuit closes
}
// DefaultConfig provides sensible defaults for the circuit breaker
var DefaultConfig = Config{
FailureThreshold: 5,
Timeout: 5 * time.Second,
SuccessThreshold: 1,
HalfOpenMaxConcurrent: 1,
IsFailure: func(c fiber.Ctx, err error) bool {
return err != nil || c.Response().StatusCode() >= http.StatusInternalServerError
},
OnOpen: func(c fiber.Ctx) error {
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
"error": "service unavailable",
})
},
OnHalfOpen: func(c fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"error": "service under recovery",
})
},
OnClose: func(c fiber.Ctx) error {
return c.Next()
},
}
// CircuitBreaker implements the circuit breaker pattern
type CircuitBreaker struct {
failureCount int64 // Count of failures (atomic)
successCount int64 // Count of successes in half-open state (atomic)
totalRequests int64 // Count of total requests (atomic)
rejectedRequests int64 // Count of rejected requests (atomic)
state State // Current state of circuit breaker
mutex sync.RWMutex // Protects state transitions
failureThreshold int // Max failures before opening circuit
timeout time.Duration // Duration to stay open before transitioning to half-open
successThreshold int // Successes required to close circuit
openTimer *time.Timer // Timer for state transition from open to half-open
ctx context.Context // Context for cancellation
cancel context.CancelFunc // Cancel function for cleanup
config Config // Configuration settings
now func() time.Time // Function for getting current time (useful for testing)
halfOpenSemaphore chan struct{} // Controls limited requests in half-open state
lastStateChange time.Time // Time of last state change
}
// New initializes a circuit breaker with the given configuration
func New(config Config) *CircuitBreaker {
// Apply default values for zero values
if config.FailureThreshold <= 0 {
config.FailureThreshold = DefaultConfig.FailureThreshold
}
if config.Timeout <= 0 {
config.Timeout = DefaultConfig.Timeout
}
if config.SuccessThreshold <= 0 {
config.SuccessThreshold = DefaultConfig.SuccessThreshold
}
if config.HalfOpenMaxConcurrent <= 0 {
config.HalfOpenMaxConcurrent = DefaultConfig.HalfOpenMaxConcurrent
}
if config.IsFailure == nil {
config.IsFailure = DefaultConfig.IsFailure
}
if config.OnOpen == nil {
config.OnOpen = DefaultConfig.OnOpen
}
if config.OnHalfOpen == nil {
config.OnHalfOpen = DefaultConfig.OnHalfOpen
}
if config.OnClose == nil {
config.OnClose = DefaultConfig.OnClose
}
ctx, cancel := context.WithCancel(context.Background())
now := time.Now()
return &CircuitBreaker{
failureThreshold: config.FailureThreshold,
timeout: config.Timeout,
successThreshold: config.SuccessThreshold,
state: StateClosed,
ctx: ctx,
cancel: cancel,
config: config,
now: time.Now,
halfOpenSemaphore: make(chan struct{}, config.HalfOpenMaxConcurrent),
lastStateChange: now,
totalRequests: 0,
rejectedRequests: 0,
}
}
// Stop cancels the circuit breaker and releases resources
func (cb *CircuitBreaker) Stop() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
if cb.openTimer != nil {
cb.openTimer.Stop()
}
cb.cancel()
}
// GetState returns the current state of the circuit breaker
func (cb *CircuitBreaker) GetState() State {
cb.mutex.RLock()
defer cb.mutex.RUnlock()
return cb.state
}
// IsOpen returns true if the circuit is open
func (cb *CircuitBreaker) IsOpen() bool {
return cb.GetState() == StateOpen
}
// Reset resets the circuit breaker to its initial closed state
func (cb *CircuitBreaker) Reset() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
// Reset counters
atomic.StoreInt64(&cb.failureCount, 0)
atomic.StoreInt64(&cb.successCount, 0)
// Reset state
cb.state = StateClosed
cb.lastStateChange = cb.now()
// Cancel any pending state transitions
if cb.openTimer != nil {
cb.openTimer.Stop()
}
}
// ForceOpen forcibly opens the circuit regardless of failure count
func (cb *CircuitBreaker) ForceOpen() {
cb.transitionToOpen()
}
// ForceClose forcibly closes the circuit regardless of current state
func (cb *CircuitBreaker) ForceClose() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
cb.state = StateClosed
cb.lastStateChange = cb.now()
atomic.StoreInt64(&cb.failureCount, 0)
atomic.StoreInt64(&cb.successCount, 0)
if cb.openTimer != nil {
cb.openTimer.Stop()
}
}
// SetTimeout updates the timeout duration
func (cb *CircuitBreaker) SetTimeout(timeout time.Duration) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
cb.timeout = timeout
}
// transitionToOpen changes state to open and schedules transition to half-open
func (cb *CircuitBreaker) transitionToOpen() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
if cb.state != StateOpen {
cb.state = StateOpen
cb.lastStateChange = cb.now()
// Stop existing timer if any
if cb.openTimer != nil {
cb.openTimer.Stop()
}
// Schedule transition to half-open after timeout
cb.openTimer = time.AfterFunc(cb.timeout, func() {
cb.transitionToHalfOpen()
})
// Reset failure counter
atomic.StoreInt64(&cb.failureCount, 0)
}
}
// transitionToHalfOpen changes state from open to half-open
func (cb *CircuitBreaker) transitionToHalfOpen() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
if cb.state == StateOpen {
cb.state = StateHalfOpen
cb.lastStateChange = cb.now()
// Reset counters
atomic.StoreInt64(&cb.failureCount, 0)
atomic.StoreInt64(&cb.successCount, 0)
// Empty the semaphore channel
select {
case <-cb.halfOpenSemaphore:
default:
}
}
}
// transitionToClosed changes state from half-open to closed
func (cb *CircuitBreaker) transitionToClosed() {
cb.mutex.Lock()
defer cb.mutex.Unlock()
if cb.state == StateHalfOpen {
cb.state = StateClosed
cb.lastStateChange = cb.now()
// Reset counters
atomic.StoreInt64(&cb.failureCount, 0)
atomic.StoreInt64(&cb.successCount, 0)
}
}
// AllowRequest determines if a request is allowed based on circuit state
func (cb *CircuitBreaker) AllowRequest() (bool, State) {
atomic.AddInt64(&cb.totalRequests, 1)
cb.mutex.RLock()
state := cb.state
cb.mutex.RUnlock()
switch state {
case StateOpen:
atomic.AddInt64(&cb.rejectedRequests, 1)
return false, state
case StateHalfOpen:
select {
case cb.halfOpenSemaphore <- struct{}{}:
return true, state
default:
atomic.AddInt64(&cb.rejectedRequests, 1)
return false, state
}
default: // StateClosed
return true, state
}
}
// ReleaseSemaphore releases a slot in the half-open semaphore
func (cb *CircuitBreaker) ReleaseSemaphore() {
select {
case <-cb.halfOpenSemaphore:
default:
}
}
// ReportSuccess increments success count and closes circuit if threshold met
func (cb *CircuitBreaker) ReportSuccess() {
cb.mutex.RLock()
currentState := cb.state
cb.mutex.RUnlock()
if currentState == StateHalfOpen {
newSuccessCount := atomic.AddInt64(&cb.successCount, 1)
if int(newSuccessCount) >= cb.successThreshold {
cb.transitionToClosed()
}
}
}
// ReportFailure increments failure count and opens circuit if threshold met
func (cb *CircuitBreaker) ReportFailure() {
cb.mutex.RLock()
currentState := cb.state
cb.mutex.RUnlock()
switch currentState {
case StateHalfOpen:
// In half-open, a single failure trips the circuit
cb.transitionToOpen()
case StateClosed:
newFailureCount := atomic.AddInt64(&cb.failureCount, 1)
if int(newFailureCount) >= cb.failureThreshold {
cb.transitionToOpen()
}
}
}
// Metrics returns basic metrics about the circuit breaker
func (cb *CircuitBreaker) Metrics() fiber.Map {
return fiber.Map{
"state": cb.GetState(),
"failures": atomic.LoadInt64(&cb.failureCount),
"successes": atomic.LoadInt64(&cb.successCount),
"totalRequests": atomic.LoadInt64(&cb.totalRequests),
"rejectedRequests": atomic.LoadInt64(&cb.rejectedRequests),
}
}
// GetStateStats returns detailed statistics about the circuit breaker
func (cb *CircuitBreaker) GetStateStats() fiber.Map {
state := cb.GetState()
return fiber.Map{
"state": state,
"failures": atomic.LoadInt64(&cb.failureCount),
"successes": atomic.LoadInt64(&cb.successCount),
"totalRequests": atomic.LoadInt64(&cb.totalRequests),
"rejectedRequests": atomic.LoadInt64(&cb.rejectedRequests),
"lastStateChange": cb.lastStateChange,
"openDuration": cb.timeout,
"failureThreshold": cb.failureThreshold,
"successThreshold": cb.successThreshold,
}
}
// HealthHandler returns a Fiber handler for checking circuit breaker status
func (cb *CircuitBreaker) HealthHandler() fiber.Handler {
return func(c fiber.Ctx) error {
state := cb.GetState()
data := fiber.Map{
"state": state,
"healthy": state == StateClosed,
}
if state == StateOpen {
return c.Status(fiber.StatusServiceUnavailable).JSON(data)
}
return c.JSON(data)
}
}
// Middleware wraps the fiber handler with circuit breaker logic
func Middleware(cb *CircuitBreaker) fiber.Handler {
return func(c fiber.Ctx) error {
allowed, state := cb.AllowRequest()
if !allowed {
// Call appropriate callback based on state
if state == StateHalfOpen && cb.config.OnHalfOpen != nil {
return cb.config.OnHalfOpen(c)
} else if state == StateOpen && cb.config.OnOpen != nil {
return cb.config.OnOpen(c)
}
return c.SendStatus(fiber.StatusServiceUnavailable)
}
// If request allowed in half-open state, ensure semaphore is released
halfOpen := state == StateHalfOpen
if halfOpen {
defer cb.ReleaseSemaphore()
}
// Execute the request
err := c.Next()
// Check if the response should be considered a failure
if cb.config.IsFailure(c, err) {
cb.ReportFailure()
} else {
cb.ReportSuccess()
// If transition to closed state just happened, trigger callback
if halfOpen && cb.GetState() == StateClosed && cb.config.OnClose != nil {
// We don't return this error as it would override the actual response
_ = cb.config.OnClose(c)
}
}
return err
}
}
================================================
FILE: v3/circuitbreaker/circuitbreaker_test.go
================================================
package circuitbreaker
import (
"encoding/json"
"errors"
"io"
"net/http/httptest"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/gofiber/fiber/v3"
"github.com/stretchr/testify/require"
)
// mockTime helps control time for deterministic testing
type mockTime struct {
mu sync.Mutex
current time.Time
}
func newMockTime(t time.Time) *mockTime {
return &mockTime{current: t}
}
func (m *mockTime) Now() time.Time {
m.mu.Lock()
defer m.mu.Unlock()
return m.current
}
func (m *mockTime) Add(d time.Duration) {
m.mu.Lock()
defer m.mu.Unlock()
m.current = m.current.Add(d)
}
// TestCircuitBreakerStates tests each state transition of the circuit breaker
func TestCircuitBreakerStates(t *testing.T) {
mockClock := newMockTime(time.Now())
// Create circuit breaker with test config
cb := New(Config{
FailureThreshold: 2,
Timeout: 5 * time.Second,
SuccessThreshold: 2,
HalfOpenMaxConcurrent: 1,
})
// Override the time function
cb.now = mockClock.Now
// Test initial state
t.Run("Initial State", func(t *testing.T) {
require.Equal(t, StateClosed, cb.GetState())
allowed, state := cb.AllowRequest()
require.True(t, allowed)
require.Equal(t, StateClosed, state)
})
// Test transition to open state
t.Run("Transition to Open", func(t *testing.T) {
// Report failures to trip the circuit
cb.ReportFailure()
require.Equal(t, StateClosed, cb.GetState())
cb.ReportFailure() // This should trip the circuit
require.Equal(t, StateOpen, cb.GetState())
allowed, state := cb.AllowRequest()
require.False(t, allowed)
require.Equal(t, StateOpen, state)
})
// Test transition to half-open state
t.Run("Transition to HalfOpen", func(t *testing.T) {
// Advance time past the timeout to trigger half-open
mockClock.Add(6 * time.Second)
// Force timer activation by checking state
// (In real usage this would happen automatically with timer)
if cb.openTimer != nil {
cb.openTimer.Stop()
cb.transitionToHalfOpen()
}
require.Equal(t, StateHalfOpen, cb.GetState())
allowed, state := cb.AllowRequest()
require.True(t, allowed)
require.Equal(t, StateHalfOpen, state)
// Release the semaphore for next test
cb.ReleaseSemaphore()
})
// Test half-open limited concurrency
t.Run("HalfOpen Limited Concurrency", func(t *testing.T) {
// Try to allow two concurrent requests when only one is permitted
allowed1, _ := cb.AllowRequest()
allowed2, _ := cb.AllowRequest()
require.True(t, allowed1)
require.False(t, allowed2)
// Release the semaphore
cb.ReleaseSemaphore()
})
// Test transition back to open on failure in half-open
t.Run("HalfOpen to Open on Failure", func(t *testing.T) {
allowed, _ := cb.AllowRequest()
require.True(t, allowed)
cb.ReportFailure()
require.Equal(t, StateOpen, cb.GetState())
// Even though we took a semaphore, it should be cleared by state transition
allowed, _ = cb.AllowRequest()
require.False(t, allowed)
})
// Test transition to half-open again
t.Run("Back to HalfOpen", func(t *testing.T) {
mockClock.Add(6 * time.Second)
// Force timer activation
if cb.openTimer != nil {
cb.openTimer.Stop()
cb.transitionToHalfOpen()
}
require.Equal(t, StateHalfOpen, cb.GetState())
})
// Test transition to closed state
t.Run("Transition to Closed", func(t *testing.T) {
allowed, _ := cb.AllowRequest()
require.True(t, allowed)
cb.ReportSuccess()
require.Equal(t, StateHalfOpen, cb.GetState())
cb.ReleaseSemaphore()
allowed, _ = cb.AllowRequest()
require.True(t, allowed)
cb.ReportSuccess() // This should close the circuit
require.Equal(t, StateClosed, cb.GetState())
cb.ReleaseSemaphore()
})
// Test proper cleanup
t.Run("Cleanup", func(t *testing.T) {
cb.Stop()
})
}
// TestCircuitBreakerCallbacks tests the callback functions
func TestCircuitBreakerCallbacks(t *testing.T) {
var (
openCalled bool
halfOpenCalled bool
closedCalled bool
)
cb := New(Config{
FailureThreshold: 2,
Timeout: 1 * time.Millisecond, // Short timeout for quick tests
SuccessThreshold: 1,
HalfOpenMaxConcurrent: 1,
OnOpen: func(c fiber.Ctx) error {
openCalled = true
return c.SendStatus(fiber.StatusServiceUnavailable)
},
OnHalfOpen: func(c fiber.Ctx) error {
halfOpenCalled = true
return c.SendStatus(fiber.StatusTooManyRequests)
},
OnClose: func(c fiber.Ctx) error {
closedCalled = true
return c.Next()
},
})
app := fiber.New()
app.Use(Middleware(cb))
app.Get("/test", func(c fiber.Ctx) error {
return c.SendString("OK")
})
// Test OnOpen callback
t.Run("OnOpen Callback", func(t *testing.T) {
// Trip the circuit
cb.ReportFailure()
cb.ReportFailure()
// Request should be rejected
req := httptest.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusServiceUnavailable, resp.StatusCode)
require.True(t, openCalled)
})
// Test OnHalfOpen callback
t.Run("OnHalfOpen Callback", func(t *testing.T) {
cb.transitionToHalfOpen()
// Acquire the one allowed request
allowed, state := cb.AllowRequest()
require.True(t, allowed)
require.Equal(t, StateHalfOpen, state)
// Second request should be rejected with OnHalfOpen callback
req := httptest.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusTooManyRequests, resp.StatusCode)
require.True(t, halfOpenCalled)
// Release the semaphore
cb.ReleaseSemaphore()
})
// Test OnClose callback
t.Run("OnClose Callback", func(t *testing.T) {
// Reset for clean test
closedCalled = false
// Get to half-open state
cb.transitionToHalfOpen()
// Create a test request
req := httptest.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode)
require.True(t, closedCalled) // OnClose should be called after successful request
})
// Clean up
cb.Stop()
}
// TestMiddleware tests the middleware functionality
func TestMiddleware(t *testing.T) {
customErr := errors.New("custom error")
cb := New(Config{
FailureThreshold: 2,
Timeout: 5 * time.Second,
SuccessThreshold: 2,
IsFailure: func(c fiber.Ctx, err error) bool {
// Count as failure if status >= 400 or has error
return err != nil || c.Response().StatusCode() >= 400
},
})
app := fiber.New()
app.Use(Middleware(cb))
// Success handler
app.Get("/success", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
})
// Client error handler - 400 series
app.Get("/client-error", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusBadRequest)
})
// Server error handler - 500 series
app.Get("/server-error", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusInternalServerError)
})
// Error handler
app.Get("/error", func(c fiber.Ctx) error {
return customErr
})
t.Run("Successful Request", func(t *testing.T) {
req := httptest.NewRequest("GET", "/success", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode)
})
t.Run("Client Error Counts as Failure", func(t *testing.T) {
// Reset to closed state
cb.transitionToClosed()
// Send client error requests
req := httptest.NewRequest("GET", "/client-error", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusBadRequest, resp.StatusCode)
// Should increment failure count - check state remains closed
require.Equal(t, StateClosed, cb.GetState())
// Second failure should trip circuit
resp, err = app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusBadRequest, resp.StatusCode)
// Circuit should now be open
require.Equal(t, StateOpen, cb.GetState())
})
t.Run("Circuit Open Rejects Requests", func(t *testing.T) {
// Circuit should be open from previous test
req := httptest.NewRequest("GET", "/success", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusServiceUnavailable, resp.StatusCode)
})
// Clean up
cb.Stop()
}
// TestConcurrentAccess tests the circuit breaker under concurrent load
func TestConcurrentAccess(t *testing.T) {
cb := New(Config{
FailureThreshold: 5,
Timeout: 100 * time.Millisecond,
SuccessThreshold: 3,
HalfOpenMaxConcurrent: 2,
})
t.Run("Concurrent Failures", func(t *testing.T) {
var wg sync.WaitGroup
// Simulate 10 goroutines reporting failures
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
cb.ReportFailure()
}()
}
wg.Wait()
// Circuit should be open after enough failures
require.Equal(t, StateOpen, cb.GetState())
})
t.Run("Concurrent Half-Open Requests", func(t *testing.T) {
// Force transition to half-open
cb.transitionToHalfOpen()
var wg sync.WaitGroup
requestAllowed := make(chan bool, 10)
// Try 10 concurrent requests
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
allowed, _ := cb.AllowRequest()
requestAllowed <- allowed
if allowed {
// Simulate request processing
time.Sleep(10 * time.Millisecond)
cb.ReleaseSemaphore()
}
}()
}
wg.Wait()
close(requestAllowed)
// Count allowed requests
allowedCount := 0
for allowed := range requestAllowed {
if allowed {
allowedCount++
}
}
// Only HalfOpenMaxConcurrent (2) requests should be allowed
require.Equal(t, cb.config.HalfOpenMaxConcurrent, allowedCount)
})
t.Run("Concurrent Successes to Close Circuit", func(t *testing.T) {
// Force transition to half-open
cb.transitionToHalfOpen()
var wg sync.WaitGroup
// Simulate 10 goroutines reporting successes
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
cb.ReportSuccess()
}()
}
wg.Wait()
// Circuit should be closed after enough successes
require.Equal(t, StateClosed, cb.GetState())
})
// Clean up
cb.Stop()
}
// TestCustomFailureDetection tests the custom failure detection logic
func TestCustomFailureDetection(t *testing.T) {
customFailureDetection := false
cb := New(Config{
FailureThreshold: 1,
IsFailure: func(c fiber.Ctx, err error) bool {
// Custom logic: mark as failure only if our flag is set
return customFailureDetection
},
})
app := fiber.New()
app.Use(Middleware(cb))
app.Get("/test", func(c fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK)
})
t.Run("Custom Success Logic", func(t *testing.T) {
customFailureDetection = false
// Even 500 status should be success with our custom logic
app.Get("/server-error", func(c fiber.Ctx) error {
c.Status(500)
return nil
})
req := httptest.NewRequest("GET", "/server-error", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, 500, resp.StatusCode)
// Circuit should remain closed
require.Equal(t, StateClosed, cb.GetState())
})
t.Run("Custom Failure Logic", func(t *testing.T) {
customFailureDetection = true
// Now even 200 status should be failure with our custom logic
req := httptest.NewRequest("GET", "/test", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode)
// Circuit should be open
require.Equal(t, StateOpen, cb.GetState())
})
// Clean up
cb.Stop()
}
// TestHalfOpenConcurrencyConfig tests that HalfOpenMaxConcurrent setting works
func TestHalfOpenConcurrencyConfig(t *testing.T) {
// Create circuit breaker with 3 concurrent requests in half-open
cb := New(Config{
FailureThreshold: 2,
Timeout: 5 * time.Second,
SuccessThreshold: 2,
HalfOpenMaxConcurrent: 3,
})
// Put circuit in half-open state
cb.transitionToOpen()
cb.transitionToHalfOpen()
// Try to get more than allowed concurrent requests
allowed1, _ := cb.AllowRequest()
allowed2, _ := cb.AllowRequest()
allowed3, _ := cb.AllowRequest()
allowed4, _ := cb.AllowRequest()
require.True(t, allowed1)
require.True(t, allowed2)
require.True(t, allowed3)
require.False(t, allowed4)
// Release all permits
cb.ReleaseSemaphore()
cb.ReleaseSemaphore()
cb.ReleaseSemaphore()
// Clean up
cb.Stop()
}
// TestCircuitBreakerReset tests the Reset method
func TestCircuitBreakerReset(t *testing.T) {
mockClock := newMockTime(time.Now())
cb := New(Config{
FailureThreshold: 2,
Timeout: 5 * time.Second,
SuccessThreshold: 2,
HalfOpenMaxConcurrent: 1,
})
cb.now = mockClock.Now
t.Run("Reset From Open State", func(t *testing.T) {
// Put circuit in open state
cb.ReportFailure()
cb.ReportFailure()
require.Equal(t, StateOpen, cb.GetState())
// Reset the circuit
cb.Reset()
// Verify state and counters
require.Equal(t, StateClosed, cb.GetState())
require.Equal(t, int64(0), atomic.LoadInt64(&cb.failureCount))
require.Equal(t, int64(0), atomic.LoadInt64(&cb.successCount))
})
t.Run("Reset From HalfOpen State", func(t *testing.T) {
// Put circuit in half-open state
cb.ReportFailure()
cb.ReportFailure()
cb.transitionToHalfOpen()
require.Equal(t, StateHalfOpen, cb.GetState())
// Take a semaphore
allowed, _ := cb.AllowRequest()
require.True(t, allowed)
// Reset the circuit
cb.Reset()
// Verify state and that new requests are allowed
require.Equal(t, StateClosed, cb.GetState())
allowed, _ = cb.AllowRequest()
require.True(t, allowed)
})
t.Run("Reset With Active Timer", func(t *testing.T) {
// Put circuit in open state with active timer
cb.ReportFailure()
cb.ReportFailure()
require.Equal(t, StateOpen, cb.GetState())
// Reset before timer expires
cb.Reset()
// Advance time past original timeout
mockClock.Add(6 * time.Second)
// Verify circuit remains closed
require.Equal(t, StateClosed, cb.GetState())
})
t.Run("Reset Updates LastStateChange", func(t *testing.T) {
initialTime := cb.lastStateChange
// Wait a moment
mockClock.Add(1 * time.Second)
// Reset the circuit
cb.Reset()
// Verify lastStateChange was updated
require.True(t, cb.lastStateChange.After(initialTime))
})
// Clean up
cb.Stop()
}
// TestCircuitBreakerForceOpen tests the ForceOpen method
func TestForceOpen(t *testing.T) {
mockClock := newMockTime(time.Now())
cb := New(Config{
FailureThreshold: 2,
Timeout: 5 * time.Second,
SuccessThreshold: 2,
HalfOpenMaxConcurrent: 1,
})
cb.now = mockClock.Now
t.Run("Force Open From Closed State", func(t *testing.T) {
require.Equal(t, StateClosed, cb.GetState())
cb.ForceOpen()
require.Equal(t, StateOpen, cb.GetState())
// Verify requests are rejected
allowed, state := cb.AllowRequest()
require.False(t, allowed)
require.Equal(t, StateOpen, state)
})
t.Run("Force Open From HalfOpen State", func(t *testing.T) {
// First get to half-open state
cb.transitionToOpen()
cb.transitionToHalfOpen()
require.Equal(t, StateHalfOpen, cb.GetState())
// Take a semaphore
allowed, _ := cb.AllowRequest()
require.True(t, allowed)
// Force open should clear semaphore
cb.ForceOpen()
require.Equal(t, StateOpen, cb.GetState())
// Verify new requests are rejected
allowed, _ = cb.AllowRequest()
require.False(t, allowed)
})
t.Run("Force Open With Active Timer", func(t *testing.T) {
cb.transitionToClosed()
cb.ForceOpen()
// Advance time past timeout
mockClock.Add(6 * time.Second)
// Should still be open since ForceOpen overrides normal timeout
require.Equal(t, StateOpen, cb.GetState())
})
t.Run("Force Open Multiple Times", func(t *testing.T) {
// Multiple force open calls should maintain open state
cb.ForceOpen()
cb.ForceOpen()
require.Equal(t, StateOpen, cb.GetState())
// Verify counters are reset each time
require.Equal(t, int64(0), atomic.LoadInt64(&cb.failureCount))
require.Equal(t, int64(0), atomic.LoadInt64(&cb.successCount))
})
// Clean up
cb.Stop()
}
// TestHealthHandler tests the health check endpoint handler
func TestHealthHandler(t *testing.T) {
cb := New(Config{
FailureThreshold: 2,
Timeout: 5 * time.Second,
SuccessThreshold: 2,
HalfOpenMaxConcurrent: 1,
})
app := fiber.New()
app.Get("/health", cb.HealthHandler())
t.Run("Healthy When Closed", func(t *testing.T) {
cb.transitionToClosed()
req := httptest.NewRequest("GET", "/health", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusOK, resp.StatusCode)
var result fiber.Map
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
err = json.Unmarshal(body, &result)
require.NoError(t, err)
require.Equal(t, string(StateClosed), result["state"])
require.Equal(t, true, result["healthy"])
})
t.Run("Unhealthy When Open", func(t *testing.T) {
cb.transitionToOpen()
req := httptest.NewRequest("GET", "/health", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, fiber.StatusServiceUnavailable, resp.StatusCode)
var result fiber.Map
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
err = json.Unmarshal(body, &result)
require.NoError(t, err)
require.Equal(t, string(StateOpen), result["state"])
require.Equal(t, false, result["healthy"])
})
t.Run("Response Content Type", func(t *testing.T) {
req := httptest.NewRequest("GET", "/health", nil)
resp, err := app.Test(req)
require.NoError(t, err)
require.Equal(t, "application/json; charset=utf-8", resp.Header.Get("Content-Type"))
})
// Clean up
cb.Stop()
}
================================================
FILE: v3/circuitbreaker/go.mod
================================================
module github.com/gofiber/contrib/v3/circuitbreaker
go 1.25.0
require (
github.com/gofiber/fiber/v3 v3.1.0
github.com/stretchr/testify v1.11.1
)
require (
github.com/andybalholm/brotli v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/gofiber/schema v1.7.1 // indirect
github.com/gofiber/utils/v2 v2.0.3 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.21 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/tinylib/msgp v1.6.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.70.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/net v0.53.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/text v0.36.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: v3/circuitbreaker/go.sum
================================================
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
github.com/gofiber/schema v1.7.1 h1:oSJBKdgP8JeIME4TQSAqlNKTU2iBB+2RNmKi8Nsc+TI=
github.com/gofiber/schema v1.7.1/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
github.com/gofiber/utils/v2 v2.0.3 h1:qJyfS/t7s7Z4+/zlU1i1pafYNP2+xLupVPgkW8ce1uI=
github.com/gofiber/utils/v2 v2.0.3/go.mod h1:GGERKU3Vhj5z6hS8YKvxL99A54DjOvTFZ0cjZnG4Lj4=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/shamaton/msgpack/v3 v3.1.0 h1:jsk0vEAqVvvS9+fTZ5/EcQ9tz860c9pWxJ4Iwecz8gU=
github.com/shamaton/msgpack/v3 v3.1.0/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA=
github.com/valyala/fasthttp v1.70.0/go.mod h1:oDZEHHkJ/Buyklg6uURmYs19442zFSnCIfX3j1FY3pE=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
================================================
FILE: v3/coraza/README.md
================================================
---
id: coraza
---
# Coraza

[](https://gofiber.io/discord)

[Coraza](https://coraza.io/) WAF middleware for Fiber.
**Compatible with Fiber v3.**
## Go version support
We only support the latest two versions of Go. Visit [https://go.dev/doc/devel/release](https://go.dev/doc/devel/release) for more information.
## Install
```sh
go get github.com/gofiber/fiber/v3
go get github.com/gofiber/contrib/v3/coraza
```
## Signature
```go
coraza.New(config ...coraza.Config) fiber.Handler
coraza.NewEngine(config coraza.Config) (*coraza.Engine, error)
```
## Config
| Property | Type | Description | Default |
|:--|:--|:--|:--|
| Next | `func(fiber.Ctx) bool` | Defines a function to skip this middleware when it returns true | `nil` |
| BlockHandler | `func(fiber.Ctx, coraza.InterruptionDetails) error` | Custom handler for blocked requests | `nil` |
| ErrorHandler | `func(fiber.Ctx, coraza.MiddlewareError) error` | Custom handler for middleware failures | `nil` |
| DirectivesFile | `[]string` | Coraza directives files loaded in order | `nil` |
| RootFS | `fs.FS` | Optional filesystem used to resolve `DirectivesFile` | `nil` |
| BlockMessage | `string` | Message returned by the built-in block handler | `"Request blocked by Web Application Firewall"` |
| LogLevel | `fiberlog.Level` | Middleware lifecycle log level | `fiberlog.LevelInfo` in `coraza.ConfigDefault` |
| RequestBodyAccess | `bool` | Enables request body inspection | `true` in `coraza.ConfigDefault` |
| MetricsCollector | `coraza.MetricsCollector` | Optional custom in-memory metrics collector | `nil` (falls back to the built-in collector) |
If you want the defaults, start from `coraza.ConfigDefault` and override the fields you need.
For zero-value-backed settings such as `RequestBodyAccess: false`, `LogLevel: fiberlog.LevelTrace`, or resetting `MetricsCollector` to the built-in default, use `ConfigDefault` or the helper methods `WithRequestBodyAccess`, `WithLogLevel`, and `WithMetricsCollector` so the choice remains explicit.
By default, the middleware starts without external rule files. Set `DirectivesFile` to load your Coraza or CRS ruleset.
Request body size follows the Fiber app `BodyLimit`.
Wildcard entries in `DirectivesFile` are expanded before Coraza initializes. If a wildcard matches no files, initialization fails with an error and the middleware does not start.
## Usage
```go
package main
import (
"log"
"github.com/gofiber/contrib/v3/coraza"
"github.com/gofiber/fiber/v3"
)
func main() {
app := fiber.New()
cfg := coraza.ConfigDefault
cfg.DirectivesFile = []string{"./conf/coraza.conf"}
app.Use(coraza.New(cfg))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
log.Fatal(app.Listen(":3000"))
}
```
## Advanced usage with Engine
Use `NewEngine` when you need explicit lifecycle control, reload support, or observability data.
```go
engineCfg := coraza.ConfigDefault
engineCfg.DirectivesFile = []string{"./conf/coraza.conf"}
engine, err := coraza.NewEngine(engineCfg)
if err != nil {
log.Fatal(err)
}
app.Use(engine.Middleware(coraza.MiddlewareConfig{
Next: func(c fiber.Ctx) bool {
return c.Path() == "/healthz"
},
BlockHandler: func(c fiber.Ctx, details coraza.InterruptionDetails) error {
return c.Status(details.StatusCode).JSON(fiber.Map{
"blocked": true,
"rule_id": details.RuleID,
})
},
}))
```
## Engine observability
The middleware does not open operational routes for you, but `Engine` exposes data-oriented methods that can be used to build your own endpoints:
- `engine.Reload()`
- `engine.MetricsSnapshot()`
- `engine.Snapshot()`
- `engine.Report()`
## Notes
- Request headers and request bodies are inspected.
- Request body size follows the Fiber app `BodyLimit`.
- Response body inspection is not supported.
- `coraza.New()` starts successfully without external rule files, but it does not load any rules until `DirectivesFile` is configured.
- Invalid configuration causes `coraza.New(...)` to panic during startup, which allows applications to fail fast.
## References
- [Coraza Docs](https://coraza.io/)
- [OWASP Core Rule Set](https://coraza.io/docs/tutorials/coreruleset)
================================================
FILE: v3/coraza/coraza.go
================================================
// Package coraza provides Coraza WAF middleware for Fiber.
package coraza
import (
"fmt"
"io"
"io/fs"
"net"
"net/http"
"os"
"path/filepath"
"reflect"
"strconv"
"strings"
"sync"
"time"
"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/experimental"
"github.com/corazawaf/coraza/v3/types"
"github.com/gofiber/fiber/v3"
fiberlog "github.com/gofiber/fiber/v3/log"
"github.com/gofiber/fiber/v3/middleware/adaptor"
)
const defaultBlockMessage = "Request blocked by Web Application Firewall"
// Config defines the configuration for the Coraza middleware and Engine.
//
// For zero-value-backed fields such as RequestBodyAccess=false,
// LogLevel=fiberlog.LevelTrace, or resetting MetricsCollector to the built-in
// default, start from ConfigDefault or use the WithRequestBodyAccess,
// WithLogLevel, and WithMetricsCollector helpers so the override remains
// explicit.
type Config struct {
// Next defines a function to skip this middleware when it returns true.
Next func(fiber.Ctx) bool
// BlockHandler customizes the response returned for interrupted requests.
BlockHandler BlockHandler
// ErrorHandler customizes the response returned for middleware failures.
ErrorHandler ErrorHandler
// DirectivesFile lists Coraza directives files to load in order.
// When empty, the engine starts without external rule files.
DirectivesFile []string
// RootFS is an optional filesystem used to resolve DirectivesFile entries.
RootFS fs.FS
// BlockMessage overrides the message used by the built-in block handler.
BlockMessage string
// LogLevel controls middleware lifecycle logging.
LogLevel fiberlog.Level
// RequestBodyAccess enables request body inspection in Coraza.
RequestBodyAccess bool
// MetricsCollector overrides the default in-memory metrics collector.
MetricsCollector MetricsCollector
logLevelSet bool
requestBodyAccessSet bool
metricsCollectorSet bool
}
// ConfigDefault provides the default Coraza configuration.
var ConfigDefault = Config{
LogLevel: fiberlog.LevelInfo,
RequestBodyAccess: true,
logLevelSet: true,
requestBodyAccessSet: true,
}
// MiddlewareConfig customizes how Engine middleware behaves for a specific mount.
type MiddlewareConfig struct {
// Next bypasses WAF inspection when it returns true.
Next func(fiber.Ctx) bool
// BlockHandler customizes the response returned for interrupted requests.
BlockHandler BlockHandler
// ErrorHandler customizes the response returned for middleware failures.
ErrorHandler ErrorHandler
}
// MiddlewareError describes an operational failure that occurred while handling a request.
type MiddlewareError struct {
// StatusCode is the HTTP status code suggested for the failure response.
StatusCode int
// Code is a stable application-level error code for the failure type.
Code string
// Message is the client-facing error message.
Message string
// Err is the underlying error when one is available.
Err error
}
// InterruptionDetails describes a Coraza interruption returned by request inspection.
type InterruptionDetails struct {
// StatusCode is the HTTP status code associated with the interruption.
StatusCode int
// Action is the Coraza action, such as "deny".
Action string
// RuleID is the matched Coraza rule identifier when available.
RuleID int
// Data contains rule-specific interruption data when available.
Data string
// Message is the message returned by the built-in block handler.
Message string
}
// BlockHandler handles requests that were interrupted by the WAF.
type BlockHandler func(fiber.Ctx, InterruptionDetails) error
// ErrorHandler handles middleware errors that prevented request inspection.
type ErrorHandler func(fiber.Ctx, MiddlewareError) error
// Engine owns a Coraza WAF instance and exposes Fiber middleware around it.
type Engine struct {
mu sync.RWMutex
waf coraza.WAF
wafWithOptions experimental.WAFWithOptions
supportsOptions bool
initErr error
activeCfg Config
lastAttemptCfg Config
blockMessage string
logLevel fiberlog.Level
metrics MetricsCollector
reloadCount uint64
lastLoadedAt time.Time
initSuccessCount uint64
initFailureCount uint64
reloadSuccessCount uint64
reloadFailureCount uint64
}
// New constructs Coraza Fiber middleware.
//
// It panics if the provided configuration cannot initialize a WAF instance.
func New(config ...Config) fiber.Handler {
cfg := ConfigDefault
if len(config) > 0 {
cfg = resolveConfig(config[0])
}
engine, err := NewEngine(cfg)
if err != nil {
panic(err)
}
return engine.Middleware(MiddlewareConfig{
Next: cfg.Next,
BlockHandler: cfg.BlockHandler,
ErrorHandler: cfg.ErrorHandler,
})
}
// NewEngine creates and initializes an Engine with the provided configuration.
func NewEngine(cfg Config) (*Engine, error) {
engine := newEngine(nil)
if err := engine.Init(cfg); err != nil {
return nil, err
}
return engine, nil
}
// Init replaces the Engine's WAF instance using the provided configuration.
//
// On failure, the last working WAF instance is kept in place and the failure is
// recorded for observability.
func (e *Engine) Init(cfg Config) error {
resolvedCfg := resolveConfig(cfg)
metrics := resolveMetricsCollector(resolvedCfg.MetricsCollector)
newWAF, err := createWAFWithConfig(resolvedCfg)
logLevel := normalizeLogLevel(resolvedCfg.LogLevel)
e.mu.Lock()
defer e.mu.Unlock()
e.lastAttemptCfg = cloneConfig(resolvedCfg)
if err != nil {
e.initErr = err
e.initFailureCount++
logWithLevel(logLevel, fiberlog.LevelError, "Coraza initialization failed", "error", err.Error())
return err
}
e.waf = newWAF
e.initErr = nil
e.setWAFOptionsStateLocked(newWAF)
e.activeCfg = cloneConfig(resolvedCfg)
e.lastLoadedAt = time.Now()
e.initSuccessCount++
e.blockMessage = resolveBlockMessage(resolvedCfg.BlockMessage)
e.logLevel = logLevel
e.metrics = metrics
logWithLevel(logLevel, fiberlog.LevelInfo, "Coraza initialized successfully", "supports_options", e.supportsOptions)
return nil
}
// SetBlockMessage overrides the default message returned by the built-in block handler.
func (e *Engine) SetBlockMessage(msg string) {
e.mu.Lock()
defer e.mu.Unlock()
e.blockMessage = resolveBlockMessage(msg)
}
// Metrics returns the Engine's metrics collector.
func (e *Engine) Metrics() MetricsCollector {
e.mu.RLock()
defer e.mu.RUnlock()
return e.metrics
}
// Middleware creates a Fiber middleware handler backed by the Engine's WAF instance.
func (e *Engine) Middleware(config ...MiddlewareConfig) fiber.Handler {
mwCfg := MiddlewareConfig{}
if len(config) > 0 {
mwCfg = config[0]
}
return func(c fiber.Ctx) error {
if mwCfg.Next != nil && mwCfg.Next(c) {
return c.Next()
}
startTime := time.Now()
metrics := e.Metrics()
metrics.RecordRequest()
defer func() {
metrics.RecordLatency(time.Since(startTime))
}()
currentWAF, currentSupportsOptions, currentWAFWithOptions, currentErr := e.snapshot()
if currentWAF == nil {
if currentErr != nil {
return e.handleError(c, mwCfg, MiddlewareError{
StatusCode: http.StatusInternalServerError,
Code: "waf_init_failed",
Message: "WAF initialization failed",
Err: currentErr,
})
}
return e.handleError(c, mwCfg, MiddlewareError{
StatusCode: http.StatusInternalServerError,
Code: "waf_not_initialized",
Message: "WAF instance not initialized",
})
}
it, mwErr := e.inspectRequest(c, currentWAF, currentSupportsOptions, currentWAFWithOptions)
if mwErr != nil {
return e.handleError(c, mwCfg, *mwErr)
}
if it != nil {
metrics.RecordBlock()
details := InterruptionDetails{
StatusCode: obtainStatusCodeFromInterruptionOrDefault(it, http.StatusForbidden),
Action: it.Action,
RuleID: it.RuleID,
Data: it.Data,
Message: e.blockMessageValue(),
}
e.log(fiberlog.LevelWarn, "Coraza request interrupted",
"rule_id", details.RuleID,
"action", details.Action,
"status", details.StatusCode)
if mwCfg.BlockHandler != nil {
return mwCfg.BlockHandler(c, details)
}
return defaultBlockHandler(c, details)
}
return c.Next()
}
}
func (e *Engine) inspectRequest(
c fiber.Ctx,
currentWAF coraza.WAF,
currentSupportsOptions bool,
currentWAFWithOptions experimental.WAFWithOptions,
) (_ *types.Interruption, mwErr *MiddlewareError) {
var tx types.Transaction
defer func() {
if r := recover(); r != nil {
e.log(fiberlog.LevelError, "Coraza panic recovered",
"panic", r,
"method", c.Method(),
"path", c.Path(),
"ip", c.IP())
mwErr = &MiddlewareError{
StatusCode: http.StatusInternalServerError,
Code: "waf_panic_recovered",
Message: "WAF internal error",
Err: fmt.Errorf("panic recovered: %v", r),
}
}
if tx != nil {
e.finishTransaction(c, tx, &mwErr)
}
}()
stdReq, err := convertFiberToStdRequest(c)
if err != nil {
return nil, &MiddlewareError{
StatusCode: http.StatusInternalServerError,
Code: "waf_request_convert_failed",
Message: "Failed to convert request",
Err: err,
}
}
if currentSupportsOptions && currentWAFWithOptions != nil {
tx = currentWAFWithOptions.NewTransactionWithOptions(experimental.Options{
Context: stdReq.Context(),
})
} else {
tx = currentWAF.NewTransaction()
}
if tx.IsRuleEngineOff() {
return nil, nil
}
it, err := processRequest(tx, stdReq, c.App().Config().BodyLimit)
if err != nil {
return nil, &MiddlewareError{
StatusCode: http.StatusInternalServerError,
Code: "waf_request_processing_failed",
Message: "WAF request processing failed",
Err: err,
}
}
return it, nil
}
func (e *Engine) finishTransaction(c fiber.Ctx, tx types.Transaction, mwErr **MiddlewareError) {
defer func() {
if r := recover(); r != nil {
e.log(fiberlog.LevelError, "Coraza cleanup panic recovered",
"panic", r,
"method", c.Method(),
"path", c.Path(),
"ip", c.IP())
if *mwErr == nil {
*mwErr = &MiddlewareError{
StatusCode: http.StatusInternalServerError,
Code: "waf_cleanup_panic_recovered",
Message: "WAF internal error",
Err: fmt.Errorf("cleanup panic recovered: %v", r),
}
}
}
}()
tx.ProcessLogging()
if err := tx.Close(); err != nil {
e.log(fiberlog.LevelDebug, "Coraza transaction close failed", "error", err.Error())
}
}
// Reload rebuilds the current WAF instance using the active configuration.
func (e *Engine) Reload() error {
e.mu.RLock()
cfg := cloneConfig(e.activeCfg)
e.mu.RUnlock()
logLevel := normalizeLogLevel(cfg.LogLevel)
logWithLevel(logLevel, fiberlog.LevelInfo, "Coraza starting manual reload")
newWAF, err := createWAFWithConfig(cfg)
if err != nil {
e.mu.Lock()
e.reloadFailureCount++
e.mu.Unlock()
logWithLevel(logLevel, fiberlog.LevelError, "Coraza reload failed", "error", err.Error())
return fmt.Errorf("failed to reload WAF: %w", err)
}
e.mu.Lock()
e.waf = newWAF
e.initErr = nil
e.setWAFOptionsStateLocked(newWAF)
e.reloadCount++
e.reloadSuccessCount++
e.lastLoadedAt = time.Now()
reloadCount := e.reloadCount
e.logLevel = logLevel
e.mu.Unlock()
logWithLevel(logLevel, fiberlog.LevelInfo, "Coraza reload completed successfully", "reload_count", reloadCount)
return nil
}
func (e *Engine) snapshot() (coraza.WAF, bool, experimental.WAFWithOptions, error) {
e.mu.RLock()
defer e.mu.RUnlock()
return e.waf, e.supportsOptions, e.wafWithOptions, e.initErr
}
func (e *Engine) setWAFOptionsStateLocked(waf coraza.WAF) {
if wafWithOptions, ok := waf.(experimental.WAFWithOptions); ok {
e.wafWithOptions = wafWithOptions
e.supportsOptions = true
return
}
e.wafWithOptions = nil
e.supportsOptions = false
}
func (e *Engine) blockMessageValue() string {
e.mu.RLock()
defer e.mu.RUnlock()
return e.blockMessage
}
func (e *Engine) handleError(c fiber.Ctx, cfg MiddlewareConfig, mwErr MiddlewareError) error {
if cfg.ErrorHandler != nil {
return cfg.ErrorHandler(c, mwErr)
}
return defaultErrorHandler(c, mwErr)
}
func newEngine(collector MetricsCollector) *Engine {
return &Engine{
blockMessage: defaultBlockMessage,
logLevel: fiberlog.LevelInfo,
metrics: resolveMetricsCollector(collector),
}
}
func isNilMetricsCollector(collector MetricsCollector) bool {
if collector == nil {
return true
}
value := reflect.ValueOf(collector)
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
return value.IsNil()
default:
return false
}
}
func resolveMetricsCollector(collector MetricsCollector) MetricsCollector {
if isNilMetricsCollector(collector) {
return NewDefaultMetricsCollector()
}
return collector
}
func (e *Engine) observabilitySnapshot() EngineSnapshot {
e.mu.RLock()
defer e.mu.RUnlock()
var lastInitError string
if e.initErr != nil {
lastInitError = e.initErr.Error()
}
configFiles := append([]string(nil), e.activeCfg.DirectivesFile...)
lastAttemptConfigFiles := append([]string(nil), e.lastAttemptCfg.DirectivesFile...)
return EngineSnapshot{
Initialized: e.waf != nil,
SupportsOptions: e.supportsOptions,
ConfigFiles: configFiles,
LastAttemptConfigFiles: lastAttemptConfigFiles,
LastInitError: lastInitError,
LastLoadedAt: e.lastLoadedAt,
InitSuccessTotal: e.initSuccessCount,
InitFailureTotal: e.initFailureCount,
ReloadSuccessTotal: e.reloadSuccessCount,
ReloadFailureTotal: e.reloadFailureCount,
ReloadCount: e.reloadCount,
}
}
func defaultBlockHandler(c fiber.Ctx, details InterruptionDetails) error {
c.Set("X-WAF-Blocked", "true")
return fiber.NewError(details.StatusCode, details.Message)
}
func defaultErrorHandler(_ fiber.Ctx, mwErr MiddlewareError) error {
return fiber.NewError(mwErr.StatusCode, mwErr.Message)
}
func processRequest(tx types.Transaction, req *http.Request, bodyLimit int) (*types.Interruption, error) {
client, cport := splitRemoteAddr(req.RemoteAddr)
tx.ProcessConnection(client, cport, "", 0)
tx.ProcessURI(req.URL.String(), req.Method, req.Proto)
for k, values := range req.Header {
for _, v := range values {
tx.AddRequestHeader(k, v)
}
}
if req.Host != "" {
tx.AddRequestHeader("Host", req.Host)
tx.SetServerName(req.Host)
}
for _, te := range req.TransferEncoding {
tx.AddRequestHeader("Transfer-Encoding", te)
}
if in := tx.ProcessRequestHeaders(); in != nil {
return in, nil
}
if tx.IsRequestBodyAccessible() && req.Body != nil && req.Body != http.NoBody {
bodyReader := io.Reader(req.Body)
if bodyLimit > 0 {
bodyReader = io.LimitReader(req.Body, int64(bodyLimit))
}
it, _, err := tx.ReadRequestBodyFrom(bodyReader)
if err != nil {
return nil, err
}
if it != nil {
return it, nil
}
}
return tx.ProcessRequestBody()
}
func obtainStatusCodeFromInterruptionOrDefault(it *types.Interruption, defaultStatusCode int) int {
if it.Action == "deny" {
if it.Status != 0 {
return it.Status
}
return http.StatusForbidden
}
return defaultStatusCode
}
func convertFiberToStdRequest(c fiber.Ctx) (*http.Request, error) {
req, err := adaptor.ConvertRequest(c, false)
if err != nil {
return nil, err
}
req.RemoteAddr = net.JoinHostPort(c.IP(), c.Port())
if req.Host == "" {
req.Host = c.Hostname()
}
return req, nil
}
func createWAFWithConfig(cfg Config) (coraza.WAF, error) {
var directivesFiles []string
logLevel := normalizeLogLevel(cfg.LogLevel)
for _, path := range cfg.DirectivesFile {
expandedPaths, err := resolveDirectivesFiles(cfg.RootFS, path, logLevel)
if err != nil {
return nil, err
}
directivesFiles = append(directivesFiles, expandedPaths...)
}
wafConfig := coraza.NewWAFConfig()
if cfg.RequestBodyAccess {
wafConfig = wafConfig.WithRequestBodyAccess()
}
if cfg.RootFS != nil {
wafConfig = wafConfig.WithRootFS(cfg.RootFS)
}
for _, path := range directivesFiles {
wafConfig = wafConfig.WithDirectivesFromFile(path)
}
return coraza.NewWAF(wafConfig)
}
func resolveDirectivesFiles(root fs.FS, path string, logLevel fiberlog.Level) ([]string, error) {
if strings.ContainsAny(path, "*?[") {
logWithLevel(logLevel, fiberlog.LevelWarn,
"Coraza directives path uses glob matching and is expanded before initialization",
"path", path,
"note", "all matching directives files will be loaded in sorted order",
)
var (
matches []string
err error
)
if root != nil {
matches, err = fs.Glob(root, path)
} else {
matches, err = filepath.Glob(path)
}
if err != nil {
return nil, fmt.Errorf("invalid Coraza directives glob %q: %w", path, err)
}
if len(matches) == 0 {
return nil, fmt.Errorf("coraza directives glob %q matched no files", path)
}
return matches, nil
}
if root != nil {
if _, err := fs.Stat(root, path); err != nil {
return nil, fmt.Errorf("coraza directives file %q not found in RootFS: %w", path, err)
}
return []string{path}, nil
}
if _, err := os.Stat(path); err != nil {
return nil, fmt.Errorf("coraza directives file %q not found: %w", path, err)
}
return []string{path}, nil
}
func splitRemoteAddr(remoteAddr string) (string, int) {
host, port, err := net.SplitHostPort(remoteAddr)
if err != nil {
return remoteAddr, 0
}
portNum, err := strconv.Atoi(port)
if err != nil {
return host, 0
}
return host, portNum
}
func cloneConfig(cfg Config) Config {
clone := cfg
clone.DirectivesFile = append([]string(nil), cfg.DirectivesFile...)
return clone
}
func resolveConfig(cfg Config) Config {
resolved := ConfigDefault
if cfg.Next != nil {
resolved.Next = cfg.Next
}
if cfg.BlockHandler != nil {
resolved.BlockHandler = cfg.BlockHandler
}
if cfg.ErrorHandler != nil {
resolved.ErrorHandler = cfg.ErrorHandler
}
if cfg.DirectivesFile != nil {
resolved.DirectivesFile = append([]string(nil), cfg.DirectivesFile...)
}
if cfg.RootFS != nil {
resolved.RootFS = cfg.RootFS
}
if cfg.BlockMessage != "" {
resolved.BlockMessage = cfg.BlockMessage
}
if cfg.logLevelSet || cfg.LogLevel != 0 {
resolved.LogLevel = normalizeLogLevel(cfg.LogLevel)
}
if cfg.requestBodyAccessSet || cfg.RequestBodyAccess {
resolved.RequestBodyAccess = cfg.RequestBodyAccess
}
if cfg.metricsCollectorSet || !isNilMetricsCollector(cfg.MetricsCollector) {
resolved.MetricsCollector = cfg.MetricsCollector
}
resolved.logLevelSet = true
resolved.requestBodyAccessSet = true
resolved.metricsCollectorSet = cfg.metricsCollectorSet || !isNilMetricsCollector(cfg.MetricsCollector)
return resolved
}
// WithLogLevel returns a copy of cfg with an explicit lifecycle log level.
func (cfg Config) WithLogLevel(level fiberlog.Level) Config {
cfg.LogLevel = level
cfg.logLevelSet = true
return cfg
}
// WithRequestBodyAccess returns a copy of cfg with explicit request body inspection behavior.
func (cfg Config) WithRequestBodyAccess(enabled bool) Config {
cfg.RequestBodyAccess = enabled
cfg.requestBodyAccessSet = true
return cfg
}
// WithMetricsCollector returns a copy of cfg with an explicit metrics collector choice.
func (cfg Config) WithMetricsCollector(collector MetricsCollector) Config {
cfg.MetricsCollector = collector
cfg.metricsCollectorSet = true
return cfg
}
func resolveBlockMessage(msg string) string {
if msg == "" {
return defaultBlockMessage
}
return msg
}
func normalizeLogLevel(level fiberlog.Level) fiberlog.Level {
switch level {
case fiberlog.LevelTrace, fiberlog.LevelDebug, fiberlog.LevelInfo, fiberlog.LevelWarn, fiberlog.LevelError:
return level
default:
return fiberlog.LevelInfo
}
}
func logWithLevel(configLevel, targetLevel fiberlog.Level, msg string, keysAndValues ...any) {
if normalizeLogLevel(configLevel) > normalizeLogLevel(targetLevel) {
return
}
switch targetLevel {
case fiberlog.LevelTrace:
fiberlog.Tracew(msg, keysAndValues...)
case fiberlog.LevelDebug:
fiberlog.Debugw(msg, keysAndValues...)
case fiberlog.LevelWarn:
fiberlog.Warnw(msg, keysAndValues...)
case fiberlog.LevelError:
fiberlog.Errorw(msg, keysAndValues...)
default:
fiberlog.Infow(msg, keysAndValues...)
}
}
func (e *Engine) currentLogLevel() fiberlog.Level {
e.mu.RLock()
defer e.mu.RUnlock()
return e.logLevel
}
func (e *Engine) log(targetLevel fiberlog.Level, msg string, keysAndValues ...any) {
logWithLevel(e.currentLogLevel(), targetLevel, msg, keysAndValues...)
}
================================================
FILE: v3/coraza/coraza_test.go
================================================
package coraza
import (
"bytes"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/corazawaf/coraza/v3/debuglog"
"github.com/corazawaf/coraza/v3/types"
"github.com/gofiber/fiber/v3"
fiberlog "github.com/gofiber/fiber/v3/log"
)
const testRules = `SecRuleEngine On
SecRequestBodyAccess On
SecRule ARGS:attack "@streq 1" "id:1001,phase:2,deny,status:403,msg:'attack detected'"`
func TestNewPanicsOnInvalidConfig(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Fatal("expected New to panic when config is invalid")
}
}()
_ = New(Config{DirectivesFile: []string{"missing.conf"}})
}
func TestNewWithoutConfigReturnsMiddleware(t *testing.T) {
app := fiber.New()
app.Use(New())
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected status 200, got %d", resp.StatusCode)
}
if string(body) != "ok" {
t.Fatalf("expected body ok, got %q", string(body))
}
}
func TestNewEngineWithLocalFile(t *testing.T) {
path := writeRuleFile(t, t.TempDir(), "local.conf", testRules)
engine, err := NewEngine(Config{
LogLevel: fiberlog.LevelInfo,
DirectivesFile: []string{path},
BlockMessage: "blocked from config",
RequestBodyAccess: true,
})
if err != nil {
t.Fatalf("expected successful initialization, got error: %v", err)
}
if engine.initErr != nil {
t.Fatalf("expected successful initialization, got error: %v", engine.initErr)
}
if engine.waf == nil {
t.Fatal("expected engine WAF to be initialized")
}
if engine.blockMessage != "blocked from config" {
t.Fatalf("expected block message to be initialized from config, got %q", engine.blockMessage)
}
}
func TestSetBlockMessageEmptyResetsDefault(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
engine.SetBlockMessage("custom block")
engine.SetBlockMessage("")
if got := engine.blockMessageValue(); got != defaultBlockMessage {
t.Fatalf("expected empty block message to restore default, got %q", got)
}
}
func TestNewEngineWithRootFS(t *testing.T) {
tempDir := t.TempDir()
writeRuleFile(t, tempDir, "rootfs.conf", testRules)
engine, err := NewEngine(Config{
DirectivesFile: []string{"rootfs.conf"},
RootFS: os.DirFS(tempDir),
RequestBodyAccess: true,
})
if err != nil {
t.Fatalf("expected RootFS initialization to succeed, got error: %v", err)
}
if engine.initErr != nil {
t.Fatalf("expected RootFS initialization to succeed, got error: %v", engine.initErr)
}
if engine.waf == nil {
t.Fatal("expected engine WAF to be initialized from RootFS")
}
}
func TestNewEngineMissingFile(t *testing.T) {
_, err := NewEngine(Config{
DirectivesFile: []string{"missing.conf"},
})
if err == nil {
t.Fatal("expected initialization to fail for missing directives file")
}
}
func TestNewReturnsMiddleware(t *testing.T) {
path := writeRuleFile(t, t.TempDir(), "test.conf", testRules)
app := fiber.New()
app.Use(New(Config{
DirectivesFile: []string{path},
RequestBodyAccess: true,
}))
app.Get("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?attack=1", nil))
defer resp.Body.Close()
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("expected status 403, got %d", resp.StatusCode)
}
}
func TestNewAppliesConfigDefaults(t *testing.T) {
bodyRules := `SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_BODY "@contains attack" "id:1002,phase:2,deny,status:403,msg:'body attack detected'"`
path := writeRuleFile(t, t.TempDir(), "body.conf", bodyRules)
app := fiber.New()
app.Use(New(Config{
DirectivesFile: []string{path},
}))
app.Post("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("payload=attack"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp := performRequest(t, app, req)
defer resp.Body.Close()
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("expected status 403 when New applies default request body access, got %d", resp.StatusCode)
}
}
func TestResolveConfigHonorsExplicitZeroValueOverrides(t *testing.T) {
resolved := resolveConfig(Config{}.WithRequestBodyAccess(false).WithLogLevel(fiberlog.LevelTrace))
if resolved.RequestBodyAccess {
t.Fatal("expected explicit request body access override to remain false")
}
if resolved.LogLevel != fiberlog.LevelTrace {
t.Fatalf("expected explicit trace log level override, got %v", resolved.LogLevel)
}
}
func TestEngineMiddlewareAllowsCleanRequest(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{})
req := httptest.NewRequest(http.MethodGet, "/?name=safe", nil)
resp := performRequest(t, app, req)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected status 200, got %d", resp.StatusCode)
}
if string(body) != "ok" {
t.Fatalf("expected body ok, got %q", string(body))
}
metrics := engine.MetricsSnapshot()
if metrics.TotalRequests != 1 || metrics.BlockedRequests != 0 {
t.Fatalf("unexpected metrics after clean request: %+v", metrics)
}
}
func TestEngineMiddlewareBlocksMaliciousRequest(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{})
req := httptest.NewRequest(http.MethodGet, "/?attack=1", nil)
resp := performRequest(t, app, req)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read response body: %v", err)
}
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("expected status 403, got %d", resp.StatusCode)
}
if resp.Header.Get("X-WAF-Blocked") != "true" {
t.Fatalf("expected X-WAF-Blocked header to be true, got %q", resp.Header.Get("X-WAF-Blocked"))
}
if !strings.Contains(string(body), defaultBlockMessage) {
t.Fatalf("expected block message in response body, got %q", string(body))
}
metrics := engine.MetricsSnapshot()
if metrics.TotalRequests != 1 || metrics.BlockedRequests != 1 {
t.Fatalf("unexpected metrics after blocked request: %+v", metrics)
}
}
func TestEngineMiddlewareBlocksMaliciousRequestBody(t *testing.T) {
bodyRules := `SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_BODY "@contains attack" "id:1002,phase:2,deny,status:403,msg:'body attack detected'"`
engine, err := newTestEngineWithRules(t, bodyRules)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{})
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("payload=attack"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp := performRequest(t, app, req)
defer resp.Body.Close()
if resp.StatusCode != http.StatusForbidden {
t.Fatalf("expected status 403 for malicious body, got %d", resp.StatusCode)
}
metrics := engine.MetricsSnapshot()
if metrics.TotalRequests != 1 || metrics.BlockedRequests != 1 {
t.Fatalf("unexpected metrics after blocked body request: %+v", metrics)
}
}
func TestEngineMiddlewareRespectsFiberBodyLimit(t *testing.T) {
bodyRules := `SecRuleEngine On
SecRequestBodyAccess On
SecRule REQUEST_BODY "@contains attack" "id:1002,phase:2,deny,status:403,msg:'body attack detected'"`
engine, err := newTestEngineWithRules(t, bodyRules)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
app := fiber.New(fiber.Config{
BodyLimit: 8,
})
app.Use(engine.Middleware())
app.Post("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("payload=attack"))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
_, err = app.Test(req)
if err == nil {
t.Fatal("expected Fiber body limit error, got nil")
}
if err.Error() != "body size exceeds the given limit" {
t.Fatalf("expected Fiber body limit error, got %v", err)
}
}
func TestNewEngineProvidesInstanceIsolation(t *testing.T) {
first, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create first engine: %v", err)
}
second, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create second engine: %v", err)
}
first.SetBlockMessage("blocked by first")
second.SetBlockMessage("blocked by second")
firstApp := newInstanceApp(first, MiddlewareConfig{})
secondApp := newInstanceApp(second, MiddlewareConfig{})
firstResp := performRequest(t, firstApp, httptest.NewRequest(http.MethodGet, "/?attack=1", nil))
defer firstResp.Body.Close()
firstBody, err := io.ReadAll(firstResp.Body)
if err != nil {
t.Fatalf("failed to read first response body: %v", err)
}
secondResp := performRequest(t, secondApp, httptest.NewRequest(http.MethodGet, "/?attack=1", nil))
defer secondResp.Body.Close()
secondBody, err := io.ReadAll(secondResp.Body)
if err != nil {
t.Fatalf("failed to read second response body: %v", err)
}
if !strings.Contains(string(firstBody), "blocked by first") {
t.Fatalf("expected first engine response to contain its block message, got %q", string(firstBody))
}
if !strings.Contains(string(secondBody), "blocked by second") {
t.Fatalf("expected second engine response to contain its block message, got %q", string(secondBody))
}
}
func TestMiddlewareConfigNextBypassesInspection(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{
Next: func(c fiber.Ctx) bool {
return c.Query("attack") == "1"
},
})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?attack=1", nil))
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected skipped request to pass through, got %d", resp.StatusCode)
}
metrics := engine.MetricsSnapshot()
if metrics.TotalRequests != 0 || metrics.BlockedRequests != 0 {
t.Fatalf("expected skipped request not to affect metrics, got %+v", metrics)
}
}
func TestMiddlewareConfigCustomBlockHandler(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{
BlockHandler: func(c fiber.Ctx, details InterruptionDetails) error {
c.Set("X-Custom-Block", "true")
return c.Status(http.StatusTeapot).JSON(fiber.Map{
"rule_id": details.RuleID,
"status": details.StatusCode,
})
},
})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?attack=1", nil))
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read custom block response body: %v", err)
}
if resp.StatusCode != http.StatusTeapot {
t.Fatalf("expected custom block status 418, got %d", resp.StatusCode)
}
if resp.Header.Get("X-Custom-Block") != "true" {
t.Fatalf("expected custom block header, got %q", resp.Header.Get("X-Custom-Block"))
}
if !strings.Contains(string(body), `"rule_id":1001`) {
t.Fatalf("expected custom block body to include rule id, got %q", string(body))
}
}
func TestMiddlewareConfigCustomErrorHandler(t *testing.T) {
engine := newEngine(NewDefaultMetricsCollector())
app := newInstanceApp(engine, MiddlewareConfig{
ErrorHandler: func(c fiber.Ctx, mwErr MiddlewareError) error {
return c.Status(http.StatusServiceUnavailable).JSON(fiber.Map{
"error_code": mwErr.Code,
"message": mwErr.Message,
})
},
})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read custom error response body: %v", err)
}
if resp.StatusCode != http.StatusServiceUnavailable {
t.Fatalf("expected custom error status 503, got %d", resp.StatusCode)
}
if !strings.Contains(string(body), `"error_code":"waf_not_initialized"`) {
t.Fatalf("expected custom error code in body, got %q", string(body))
}
}
func TestEngineReportIncludesLifecycleSnapshot(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
report := engine.Report()
if !report.Engine.Initialized {
t.Fatal("expected report to include initialized engine state")
}
if report.Engine.InitSuccessTotal != 1 {
t.Fatalf("expected init success count to be 1, got %d", report.Engine.InitSuccessTotal)
}
if len(report.Engine.ConfigFiles) != 1 {
t.Fatalf("expected one config file in report, got %+v", report.Engine.ConfigFiles)
}
}
func TestMetricsSnapshotHandlesNilCollectorSnapshot(t *testing.T) {
engine := newEngine(nilSnapshotCollector{})
snapshot := engine.MetricsSnapshot()
if snapshot.TotalRequests != 0 || snapshot.BlockedRequests != 0 || snapshot.AvgLatencyMs != 0 || snapshot.BlockRate != 0 {
t.Fatalf("expected zero-value metrics snapshot, got %+v", snapshot)
}
if snapshot.Timestamp.IsZero() {
t.Fatal("expected metrics snapshot timestamp to be populated")
}
}
func TestNewEngineFallsBackToDefaultCollectorForTypedNilMetricsCollector(t *testing.T) {
var collector MetricsCollector = (*nilPtrSnapshotCollector)(nil)
engine := newEngine(collector)
if engine.Metrics() == nil {
t.Fatal("expected typed-nil metrics collector to fall back to the default collector")
}
app := newInstanceApp(engine, MiddlewareConfig{})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/", nil))
defer resp.Body.Close()
if resp.StatusCode != http.StatusInternalServerError {
t.Fatalf("expected status 500 with uninitialized WAF, got %d", resp.StatusCode)
}
snapshot := engine.MetricsSnapshot()
if snapshot.TotalRequests != 1 {
t.Fatalf("expected fallback collector to record one request, got %+v", snapshot)
}
}
func TestEngineInitFailureKeepsLastWorkingWAF(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{})
allowedBefore := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?name=safe", nil))
defer allowedBefore.Body.Close()
if allowedBefore.StatusCode != http.StatusOK {
t.Fatalf("expected status 200 before failed reinit, got %d", allowedBefore.StatusCode)
}
err = engine.Init(Config{DirectivesFile: []string{filepath.Join(t.TempDir(), "missing.conf")}})
if err == nil {
t.Fatal("expected reinitialization with missing config to fail")
}
allowedAfter := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?name=safe", nil))
defer allowedAfter.Body.Close()
body, err := io.ReadAll(allowedAfter.Body)
if err != nil {
t.Fatalf("failed to read response body after failed reinit: %v", err)
}
if allowedAfter.StatusCode != http.StatusOK {
t.Fatalf("expected last working WAF to continue serving after failed reinit, got %d with body %q", allowedAfter.StatusCode, string(body))
}
snapshot := engine.Snapshot()
if snapshot.LastInitError == "" {
t.Fatal("expected engine snapshot to retain the last initialization error for observability")
}
if len(snapshot.ConfigFiles) != 1 || !strings.HasSuffix(snapshot.ConfigFiles[0], "test.conf") {
t.Fatalf("expected active config to remain unchanged, got %+v", snapshot.ConfigFiles)
}
if len(snapshot.LastAttemptConfigFiles) != 1 || !strings.HasSuffix(snapshot.LastAttemptConfigFiles[0], "missing.conf") {
t.Fatalf("expected last attempted config to be reported, got %+v", snapshot.LastAttemptConfigFiles)
}
}
func TestMiddlewareFailsClosedWhenWAFPanicOccurs(t *testing.T) {
engine := newEngine(NewDefaultMetricsCollector())
engine.waf = fakePanicWAF{}
app := newInstanceApp(engine, MiddlewareConfig{})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?name=safe", nil))
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("failed to read panic recovery response body: %v", err)
}
if resp.StatusCode != http.StatusInternalServerError {
t.Fatalf("expected status 500 when WAF panics, got %d", resp.StatusCode)
}
if !strings.Contains(string(body), "WAF internal error") {
t.Fatalf("expected WAF internal error response, got %q", string(body))
}
metrics := engine.MetricsSnapshot()
if metrics.TotalRequests != 1 || metrics.BlockedRequests != 0 {
t.Fatalf("unexpected metrics after panic recovery: %+v", metrics)
}
}
func TestEngineSnapshotTracksLifecycleCounters(t *testing.T) {
engine, err := newTestEngine(t)
if err != nil {
t.Fatalf("failed to create engine: %v", err)
}
if err := engine.Reload(); err != nil {
t.Fatalf("expected reload to succeed, got %v", err)
}
snapshot := engine.Snapshot()
if snapshot.ReloadSuccessTotal != 1 {
t.Fatalf("expected ReloadSuccessTotal=1, got %#v", snapshot.ReloadSuccessTotal)
}
if snapshot.InitSuccessTotal != 1 {
t.Fatalf("expected InitSuccessTotal=1, got %#v", snapshot.InitSuccessTotal)
}
if snapshot.ReloadCount != 1 {
t.Fatalf("expected ReloadCount=1, got %#v", snapshot.ReloadCount)
}
}
func TestEngineInitReplacesMetricsCollectorWhenProvided(t *testing.T) {
initialCollector := &countingCollector{}
engine := newEngine(initialCollector)
path := writeRuleFile(t, t.TempDir(), "collector.conf", testRules)
replacementCollector := &countingCollector{}
if err := engine.Init(Config{
DirectivesFile: []string{path},
RequestBodyAccess: true,
MetricsCollector: replacementCollector,
}); err != nil {
t.Fatalf("expected init to succeed, got %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?name=safe", nil))
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected status 200 after init, got %d", resp.StatusCode)
}
if initialCollector.requests != 0 {
t.Fatalf("expected initial collector to stop receiving updates, got %d requests", initialCollector.requests)
}
if replacementCollector.requests != 1 {
t.Fatalf("expected replacement collector to record one request, got %d", replacementCollector.requests)
}
}
func TestEngineInitResetsMetricsCollectorToDefaultWhenOmitted(t *testing.T) {
initialCollector := &countingCollector{}
engine, err := NewEngine(Config{
DirectivesFile: []string{writeRuleFile(t, t.TempDir(), "collector.conf", testRules)},
}.WithMetricsCollector(initialCollector))
if err != nil {
t.Fatalf("failed to create engine with custom collector: %v", err)
}
reloadPath := writeRuleFile(t, t.TempDir(), "collector-reload.conf", testRules)
if err := engine.Init(Config{
DirectivesFile: []string{reloadPath},
}); err != nil {
t.Fatalf("expected reinit without collector to succeed, got %v", err)
}
app := newInstanceApp(engine, MiddlewareConfig{})
resp := performRequest(t, app, httptest.NewRequest(http.MethodGet, "/?name=safe", nil))
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("expected status 200 after collector reset, got %d", resp.StatusCode)
}
if initialCollector.requests != 0 {
t.Fatalf("expected original custom collector to stop receiving updates, got %d requests", initialCollector.requests)
}
snapshot := engine.MetricsSnapshot()
if snapshot.TotalRequests != 1 {
t.Fatalf("expected default collector to record one request after reset, got %+v", snapshot)
}
}
func TestReloadWithoutDirectivesSucceeds(t *testing.T) {
engine, err := NewEngine(Config{
RequestBodyAccess: true,
})
if err != nil {
t.Fatalf("failed to initialize engine without directives: %v", err)
}
if err := engine.Reload(); err != nil {
t.Fatalf("expected reload without directives to succeed, got %v", err)
}
snapshot := engine.Snapshot()
if snapshot.ReloadSuccessTotal != 1 {
t.Fatalf("expected ReloadSuccessTotal=1, got %#v", snapshot.ReloadSuccessTotal)
}
if snapshot.InitSuccessTotal != 1 {
t.Fatalf("expected InitSuccessTotal=1, got %#v", snapshot.InitSuccessTotal)
}
if snapshot.ReloadCount != 1 {
t.Fatalf("expected ReloadCount=1, got %#v", snapshot.ReloadCount)
}
}
func TestNewEngineWildcardDirectivesRequireMatch(t *testing.T) {
_, err := NewEngine(Config{
DirectivesFile: []string{filepath.Join(t.TempDir(), "*.conf")},
})
if err == nil {
t.Fatal("expected wildcard directives with no matches to fail")
}
if !strings.Contains(err.Error(), "matched no files") {
t.Fatalf("expected wildcard match error, got %v", err)
}
}
func TestNewEngineQuestionMarkGlobDirectivesMatch(t *testing.T) {
rootDir := t.TempDir()
writeRuleFile(t, rootDir, "rule-1.conf", testRules)
engine, err := NewEngine(Config{
DirectivesFile: []string{filepath.Join(rootDir, "rule-?.conf")},
RequestBodyAccess: true,
})
if err != nil {
t.Fatalf("expected question mark glob to match directives file, got %v", err)
}
if engine == nil || engine.waf == nil {
t.Fatal("expected engine to initialize from question mark glob")
}
}
func TestNewEngineCharacterClassGlobDirectivesMatch(t *testing.T) {
rootDir := t.TempDir()
writeRuleFile(t, rootDir, "rule-1.conf", testRules)
engine, err := NewEngine(Config{
DirectivesFile: []string{filepath.Join(rootDir, "rule-[12].conf")},
RequestBodyAccess: true,
})
if err != nil {
t.Fatalf("expected character class glob to match directives file, got %v", err)
}
if engine == nil || engine.waf == nil {
t.Fatal("expected engine to initialize from character class glob")
}
}
func TestNewEngineWildcardDirectivesWithRootFSRequireMatch(t *testing.T) {
rootDir := t.TempDir()
_, err := NewEngine(Config{
DirectivesFile: []string{"*.conf"},
RootFS: os.DirFS(rootDir),
})
if err == nil {
t.Fatal("expected RootFS wildcard directives with no matches to fail")
}
if !strings.Contains(err.Error(), "matched no files") {
t.Fatalf("expected wildcard match error, got %v", err)
}
}
func TestDefaultMetricsCollectorRecordLatencyUsesOnlineAverage(t *testing.T) {
collector := NewDefaultMetricsCollector().(*defaultMetricsCollector)
collector.RecordLatency(time.Millisecond)
collector.RecordLatency(3 * time.Millisecond)
collector.RecordLatency(-time.Millisecond)
snapshot := collector.GetMetrics()
if snapshot == nil {
t.Fatal("expected metrics snapshot")
}
if collector.latencyCount != 2 {
t.Fatalf("expected negative latency sample to be ignored, got %d", collector.latencyCount)
}
if snapshot.AvgLatencyMs != 2 {
t.Fatalf("expected average latency to be 2ms, got %v", snapshot.AvgLatencyMs)
}
}
func newInstanceApp(engine *Engine, cfg MiddlewareConfig) *fiber.App {
app := fiber.New()
app.Use(engine.Middleware(cfg))
app.All("/", func(c fiber.Ctx) error {
return c.SendString("ok")
})
return app
}
func newTestEngine(t *testing.T) (*Engine, error) {
t.Helper()
return newTestEngineWithRules(t, testRules)
}
func newTestEngineWithRules(t *testing.T, rules string) (*Engine, error) {
t.Helper()
path := writeRuleFile(t, t.TempDir(), "test.conf", rules)
return NewEngine(Config{
LogLevel: fiberlog.LevelInfo,
DirectivesFile: []string{path},
RequestBodyAccess: true,
})
}
func writeRuleFile(t *testing.T, dir, name, contents string) string {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(contents), 0o600); err != nil {
t.Fatalf("failed to write directives file: %v", err)
}
return path
}
func performRequest(t *testing.T, app *fiber.App, req *http.Request) *http.Response {
t.Helper()
resp, err := app.Test(req)
if err != nil {
t.Fatalf("request failed: %v", err)
}
return resp
}
type nilSnapshotCollector struct{}
func (nilSnapshotCollector) RecordRequest() {}
func (nilSnapshotCollector) RecordBlock() {}
func (nilSnapshotCollector) RecordLatency(time.Duration) {}
func (nilSnapshotCollector) GetMetrics() *MetricsSnapshot { return nil }
func (nilSnapshotCollector) Reset() {}
type nilPtrSnapshotCollector struct{}
func (*nilPtrSnapshotCollector) RecordRequest() {}
func (*nilPtrSnapshotCollector) RecordBlock() {}
func (*nilPtrSnapshotCollector) RecordLatency(time.Duration) {}
func (*nilPtrSnapshotCollector) GetMetrics() *MetricsSnapshot { return nil }
func (*nilPtrSnapshotCollector) Reset() {}
type countingCollector struct {
requests uint64
blocks uint64
}
func (c *countingCollector) RecordRequest() { c.requests++ }
func (c *countingCollector) RecordBlock() { c.blocks++ }
func (c *countingCollector) RecordLatency(time.Duration) {}
func (c *countingCollector) GetMetrics() *MetricsSnapshot {
return &MetricsSnapshot{
Total
gitextract_6_bal560/
├── .github/
│ ├── CODEOWNERS
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug-report.yaml
│ │ ├── feature-request.yaml
│ │ └── question.yaml
│ ├── dependabot.yml
│ ├── release-plan.yml
│ ├── release.yml
│ ├── scripts/
│ │ └── parallel-go-test.sh
│ └── workflows/
│ ├── after-release.yml
│ ├── auto-labeler.yml
│ ├── cleanup-release-draft.yml
│ ├── dependabot-on-demand.yml
│ ├── dependabot_automerge.yml
│ ├── lint.yml
│ ├── release-drafter.yml
│ ├── sync-docs.yml
│ ├── test-casbin.yml
│ ├── test-circuitbreaker.yml
│ ├── test-coraza.yml
│ ├── test-fgprof.yml
│ ├── test-hcaptcha.yml
│ ├── test-i18n.yml
│ ├── test-jwt.yml
│ ├── test-loadshed.yml
│ ├── test-monitor.yml
│ ├── test-newrelic.yml
│ ├── test-opa.yml
│ ├── test-otel.yml
│ ├── test-paseto.yml
│ ├── test-sentry.yml
│ ├── test-socketio.yml
│ ├── test-swaggerui.yml
│ ├── test-swaggo.yml
│ ├── test-testcontainers.yml
│ ├── test-websocket.yml
│ ├── test-zap.yml
│ ├── test-zerolog.yml
│ └── weekly-release.yml
├── .gitignore
├── LICENSE
├── README.md
├── go.work
└── v3/
├── .golangci.yml
├── README.md
├── casbin/
│ ├── README.md
│ ├── casbin.go
│ ├── casbin_test.go
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── options.go
│ └── utils.go
├── circuitbreaker/
│ ├── README.md
│ ├── circuitbreaker.go
│ ├── circuitbreaker_test.go
│ ├── go.mod
│ └── go.sum
├── coraza/
│ ├── README.md
│ ├── coraza.go
│ ├── coraza_test.go
│ ├── go.mod
│ ├── go.sum
│ └── metrics.go
├── fgprof/
│ ├── README.md
│ ├── config.go
│ ├── fgprof.go
│ ├── fgprof_test.go
│ ├── go.mod
│ └── go.sum
├── hcaptcha/
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── hcaptcha.go
│ └── hcaptcha_test.go
├── i18n/
│ ├── README.md
│ ├── config.go
│ ├── embed.go
│ ├── embed_test.go
│ ├── example/
│ │ ├── localize/
│ │ │ ├── en.yaml
│ │ │ └── zh.yaml
│ │ ├── localizeJSON/
│ │ │ ├── en.json
│ │ │ └── zh.json
│ │ └── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── i18n.go
│ └── i18n_test.go
├── jwt/
│ ├── README.md
│ ├── config.go
│ ├── config_test.go
│ ├── crypto.go
│ ├── go.mod
│ ├── go.sum
│ ├── jwt.go
│ └── jwt_test.go
├── loadshed/
│ ├── README.md
│ ├── cpu.go
│ ├── go.mod
│ ├── go.sum
│ ├── loadshed.go
│ └── loadshed_test.go
├── monitor/
│ ├── README.md
│ ├── config.go
│ ├── config_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── index.go
│ ├── monitor.go
│ └── monitor_test.go
├── newrelic/
│ ├── README.md
│ ├── fiber.go
│ ├── fiber_test.go
│ ├── go.mod
│ └── go.sum
├── opa/
│ ├── README.md
│ ├── fiber.go
│ ├── fiber_test.go
│ ├── go.mod
│ └── go.sum
├── otel/
│ ├── README.md
│ ├── config.go
│ ├── doc.go
│ ├── example/
│ │ ├── Dockerfile
│ │ ├── README.md
│ │ ├── docker-compose.yml
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── server.go
│ ├── fiber.go
│ ├── fiber_context_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── internal/
│ │ ├── http.go
│ │ └── http_test.go
│ ├── otel_test/
│ │ └── fiber_test.go
│ └── semconv.go
├── paseto/
│ ├── README.md
│ ├── config.go
│ ├── config_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── helpers.go
│ ├── paseto.go
│ ├── paseto_test.go
│ └── payload.go
├── sentry/
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── sentry.go
│ └── sentry_test.go
├── socketio/
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ ├── socketio.go
│ └── socketio_test.go
├── swaggerui/
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ ├── swagger.go
│ ├── swagger.json
│ ├── swagger.yaml
│ ├── swagger_missing.json
│ └── swagger_test.go
├── swaggo/
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── index.go
│ ├── swagger.go
│ └── swagger_test.go
├── testcontainers/
│ ├── README.md
│ ├── config.go
│ ├── examples_test.go
│ ├── go.mod
│ ├── go.sum
│ ├── testcontainers.go
│ ├── testcontainers_test.go
│ └── testcontainers_unit_test.go
├── websocket/
│ ├── README.md
│ ├── go.mod
│ ├── go.sum
│ ├── websocket.go
│ └── websocket_test.go
├── zap/
│ ├── .gitignore
│ ├── README.md
│ ├── config.go
│ ├── go.mod
│ ├── go.sum
│ ├── logger.go
│ ├── logger_test.go
│ ├── zap.go
│ └── zap_test.go
└── zerolog/
├── README.md
├── config.go
├── go.mod
├── go.sum
├── zerolog.go
└── zerolog_test.go
SYMBOL INDEX (884 symbols across 79 files)
FILE: v3/casbin/casbin.go
type Middleware (line 10) | type Middleware struct
method RequiresPermissions (line 28) | func (m *Middleware) RequiresPermissions(permissions []string, opts .....
method RoutePermission (line 71) | func (m *Middleware) RoutePermission() fiber.Handler {
method RequiresRoles (line 90) | func (m *Middleware) RequiresRoles(roles []string, opts ...Option) fib...
function New (line 15) | func New(config ...Config) *Middleware {
FILE: v3/casbin/casbin_test.go
constant modelConf (line 23) | modelConf = `
constant policyList (line 39) | policyList = `
type mockAdapter (line 58) | type mockAdapter struct
method LoadPolicy (line 68) | func (ma *mockAdapter) LoadPolicy(model model.Model) error {
method SavePolicy (line 86) | func (ma *mockAdapter) SavePolicy(model model.Model) error {
method AddPolicy (line 90) | func (ma *mockAdapter) AddPolicy(sec string, ptype string, rule []stri...
method RemovePolicy (line 94) | func (ma *mockAdapter) RemovePolicy(sec string, ptype string, rule []s...
method RemoveFilteredPolicy (line 98) | func (ma *mockAdapter) RemoveFilteredPolicy(sec string, ptype string, ...
function newMockAdapter (line 62) | func newMockAdapter(text string) *mockAdapter {
function setup (line 102) | func setup() (*casbin.Enforcer, error) {
function Test_RequiresPermission (line 116) | func Test_RequiresPermission(t *testing.T) {
function Test_RequiresRoles (line 228) | func Test_RequiresRoles(t *testing.T) {
function Test_RoutePermission (line 340) | func Test_RoutePermission(t *testing.T) {
FILE: v3/casbin/config.go
type Config (line 11) | type Config struct
function configDefault (line 47) | func configDefault(config ...Config) (Config, error) {
FILE: v3/casbin/options.go
constant MatchAllRule (line 6) | MatchAllRule ValidationRule = iota
constant AtLeastOneRule (line 7) | AtLeastOneRule
type ValidationRule (line 16) | type ValidationRule
type PermissionParserFunc (line 19) | type PermissionParserFunc
type OptionFunc (line 20) | type OptionFunc
method apply (line 32) | func (of OptionFunc) apply(o *Options) {
type Option (line 22) | type Option interface
type Options (line 26) | type Options struct
function WithValidationRule (line 36) | func WithValidationRule(vr ValidationRule) Option {
function WithPermissionParser (line 42) | func WithPermissionParser(pp PermissionParserFunc) Option {
function PermissionParserWithSeperator (line 48) | func PermissionParserWithSeperator(sep string) PermissionParserFunc {
function optionsDefault (line 55) | func optionsDefault(opts ...Option) Options {
FILE: v3/casbin/utils.go
function containsString (line 3) | func containsString(s []string, v string) bool {
function stringSliceToInterfaceSlice (line 12) | func stringSliceToInterfaceSlice(s []string) []interface{} {
FILE: v3/circuitbreaker/circuitbreaker.go
type State (line 14) | type State
constant StateClosed (line 17) | StateClosed State = "closed"
constant StateOpen (line 18) | StateOpen State = "open"
constant StateHalfOpen (line 19) | StateHalfOpen State = "half-open"
type Config (line 23) | type Config struct
type CircuitBreaker (line 65) | type CircuitBreaker struct
method Stop (line 132) | func (cb *CircuitBreaker) Stop() {
method GetState (line 143) | func (cb *CircuitBreaker) GetState() State {
method IsOpen (line 150) | func (cb *CircuitBreaker) IsOpen() bool {
method Reset (line 155) | func (cb *CircuitBreaker) Reset() {
method ForceOpen (line 174) | func (cb *CircuitBreaker) ForceOpen() {
method ForceClose (line 179) | func (cb *CircuitBreaker) ForceClose() {
method SetTimeout (line 194) | func (cb *CircuitBreaker) SetTimeout(timeout time.Duration) {
method transitionToOpen (line 202) | func (cb *CircuitBreaker) transitionToOpen() {
method transitionToHalfOpen (line 226) | func (cb *CircuitBreaker) transitionToHalfOpen() {
method transitionToClosed (line 247) | func (cb *CircuitBreaker) transitionToClosed() {
method AllowRequest (line 262) | func (cb *CircuitBreaker) AllowRequest() (bool, State) {
method ReleaseSemaphore (line 287) | func (cb *CircuitBreaker) ReleaseSemaphore() {
method ReportSuccess (line 295) | func (cb *CircuitBreaker) ReportSuccess() {
method ReportFailure (line 309) | func (cb *CircuitBreaker) ReportFailure() {
method Metrics (line 327) | func (cb *CircuitBreaker) Metrics() fiber.Map {
method GetStateStats (line 338) | func (cb *CircuitBreaker) GetStateStats() fiber.Map {
method HealthHandler (line 355) | func (cb *CircuitBreaker) HealthHandler() fiber.Handler {
function New (line 85) | func New(config Config) *CircuitBreaker {
function Middleware (line 373) | func Middleware(cb *CircuitBreaker) fiber.Handler {
FILE: v3/circuitbreaker/circuitbreaker_test.go
type mockTime (line 18) | type mockTime struct
method Now (line 27) | func (m *mockTime) Now() time.Time {
method Add (line 33) | func (m *mockTime) Add(d time.Duration) {
function newMockTime (line 23) | func newMockTime(t time.Time) *mockTime {
function TestCircuitBreakerStates (line 40) | func TestCircuitBreakerStates(t *testing.T) {
function TestCircuitBreakerCallbacks (line 162) | func TestCircuitBreakerCallbacks(t *testing.T) {
function TestMiddleware (line 254) | func TestMiddleware(t *testing.T) {
function TestConcurrentAccess (line 335) | func TestConcurrentAccess(t *testing.T) {
function TestCustomFailureDetection (line 425) | func TestCustomFailureDetection(t *testing.T) {
function TestHalfOpenConcurrencyConfig (line 481) | func TestHalfOpenConcurrencyConfig(t *testing.T) {
function TestCircuitBreakerReset (line 515) | func TestCircuitBreakerReset(t *testing.T) {
function TestForceOpen (line 595) | func TestForceOpen(t *testing.T) {
function TestHealthHandler (line 664) | func TestHealthHandler(t *testing.T) {
FILE: v3/coraza/coraza.go
constant defaultBlockMessage (line 26) | defaultBlockMessage = "Request blocked by Web Application Firewall"
type Config (line 35) | type Config struct
method WithLogLevel (line 701) | func (cfg Config) WithLogLevel(level fiberlog.Level) Config {
method WithRequestBodyAccess (line 708) | func (cfg Config) WithRequestBodyAccess(enabled bool) Config {
method WithMetricsCollector (line 715) | func (cfg Config) WithMetricsCollector(collector MetricsCollector) Con...
type MiddlewareConfig (line 70) | type MiddlewareConfig struct
type MiddlewareError (line 80) | type MiddlewareError struct
type InterruptionDetails (line 92) | type InterruptionDetails struct
type BlockHandler (line 106) | type BlockHandler
type ErrorHandler (line 109) | type ErrorHandler
type Engine (line 112) | type Engine struct
method Init (line 169) | func (e *Engine) Init(cfg Config) error {
method SetBlockMessage (line 203) | func (e *Engine) SetBlockMessage(msg string) {
method Metrics (line 210) | func (e *Engine) Metrics() MetricsCollector {
method Middleware (line 217) | func (e *Engine) Middleware(config ...MiddlewareConfig) fiber.Handler {
method inspectRequest (line 285) | func (e *Engine) inspectRequest(
method finishTransaction (line 349) | func (e *Engine) finishTransaction(c fiber.Ctx, tx types.Transaction, ...
method Reload (line 376) | func (e *Engine) Reload() error {
method snapshot (line 408) | func (e *Engine) snapshot() (coraza.WAF, bool, experimental.WAFWithOpt...
method setWAFOptionsStateLocked (line 415) | func (e *Engine) setWAFOptionsStateLocked(waf coraza.WAF) {
method blockMessageValue (line 426) | func (e *Engine) blockMessageValue() string {
method handleError (line 432) | func (e *Engine) handleError(c fiber.Ctx, cfg MiddlewareConfig, mwErr ...
method observabilitySnapshot (line 470) | func (e *Engine) observabilitySnapshot() EngineSnapshot {
method currentLogLevel (line 757) | func (e *Engine) currentLogLevel() fiberlog.Level {
method log (line 763) | func (e *Engine) log(targetLevel fiberlog.Level, msg string, keysAndVa...
function New (line 137) | func New(config ...Config) fiber.Handler {
function NewEngine (line 156) | func NewEngine(cfg Config) (*Engine, error) {
function newEngine (line 440) | func newEngine(collector MetricsCollector) *Engine {
function isNilMetricsCollector (line 448) | func isNilMetricsCollector(collector MetricsCollector) bool {
function resolveMetricsCollector (line 462) | func resolveMetricsCollector(collector MetricsCollector) MetricsCollector {
function defaultBlockHandler (line 497) | func defaultBlockHandler(c fiber.Ctx, details InterruptionDetails) error {
function defaultErrorHandler (line 502) | func defaultErrorHandler(_ fiber.Ctx, mwErr MiddlewareError) error {
function processRequest (line 506) | func processRequest(tx types.Transaction, req *http.Request, bodyLimit i...
function obtainStatusCodeFromInterruptionOrDefault (line 549) | func obtainStatusCodeFromInterruptionOrDefault(it *types.Interruption, d...
function convertFiberToStdRequest (line 560) | func convertFiberToStdRequest(c fiber.Ctx) (*http.Request, error) {
function createWAFWithConfig (line 574) | func createWAFWithConfig(cfg Config) (coraza.WAF, error) {
function resolveDirectivesFiles (line 601) | func resolveDirectivesFiles(root fs.FS, path string, logLevel fiberlog.L...
function splitRemoteAddr (line 642) | func splitRemoteAddr(remoteAddr string) (string, int) {
function cloneConfig (line 656) | func cloneConfig(cfg Config) Config {
function resolveConfig (line 662) | func resolveConfig(cfg Config) Config {
function resolveBlockMessage (line 721) | func resolveBlockMessage(msg string) string {
function normalizeLogLevel (line 729) | func normalizeLogLevel(level fiberlog.Level) fiberlog.Level {
function logWithLevel (line 738) | func logWithLevel(configLevel, targetLevel fiberlog.Level, msg string, k...
FILE: v3/coraza/coraza_test.go
constant testRules (line 20) | testRules = `SecRuleEngine On
function TestNewPanicsOnInvalidConfig (line 24) | func TestNewPanicsOnInvalidConfig(t *testing.T) {
function TestNewWithoutConfigReturnsMiddleware (line 34) | func TestNewWithoutConfigReturnsMiddleware(t *testing.T) {
function TestNewEngineWithLocalFile (line 57) | func TestNewEngineWithLocalFile(t *testing.T) {
function TestSetBlockMessageEmptyResetsDefault (line 80) | func TestSetBlockMessageEmptyResetsDefault(t *testing.T) {
function TestNewEngineWithRootFS (line 94) | func TestNewEngineWithRootFS(t *testing.T) {
function TestNewEngineMissingFile (line 115) | func TestNewEngineMissingFile(t *testing.T) {
function TestNewReturnsMiddleware (line 124) | func TestNewReturnsMiddleware(t *testing.T) {
function TestNewAppliesConfigDefaults (line 144) | func TestNewAppliesConfigDefaults(t *testing.T) {
function TestResolveConfigHonorsExplicitZeroValueOverrides (line 170) | func TestResolveConfigHonorsExplicitZeroValueOverrides(t *testing.T) {
function TestEngineMiddlewareAllowsCleanRequest (line 181) | func TestEngineMiddlewareAllowsCleanRequest(t *testing.T) {
function TestEngineMiddlewareBlocksMaliciousRequest (line 211) | func TestEngineMiddlewareBlocksMaliciousRequest(t *testing.T) {
function TestEngineMiddlewareBlocksMaliciousRequestBody (line 244) | func TestEngineMiddlewareBlocksMaliciousRequestBody(t *testing.T) {
function TestEngineMiddlewareRespectsFiberBodyLimit (line 271) | func TestEngineMiddlewareRespectsFiberBodyLimit(t *testing.T) {
function TestNewEngineProvidesInstanceIsolation (line 301) | func TestNewEngineProvidesInstanceIsolation(t *testing.T) {
function TestMiddlewareConfigNextBypassesInspection (line 339) | func TestMiddlewareConfigNextBypassesInspection(t *testing.T) {
function TestMiddlewareConfigCustomBlockHandler (line 364) | func TestMiddlewareConfigCustomBlockHandler(t *testing.T) {
function TestMiddlewareConfigCustomErrorHandler (line 398) | func TestMiddlewareConfigCustomErrorHandler(t *testing.T) {
function TestEngineReportIncludesLifecycleSnapshot (line 424) | func TestEngineReportIncludesLifecycleSnapshot(t *testing.T) {
function TestMetricsSnapshotHandlesNilCollectorSnapshot (line 442) | func TestMetricsSnapshotHandlesNilCollectorSnapshot(t *testing.T) {
function TestNewEngineFallsBackToDefaultCollectorForTypedNilMetricsCollector (line 455) | func TestNewEngineFallsBackToDefaultCollectorForTypedNilMetricsCollector...
function TestEngineInitFailureKeepsLastWorkingWAF (line 478) | func TestEngineInitFailureKeepsLastWorkingWAF(t *testing.T) {
function TestMiddlewareFailsClosedWhenWAFPanicOccurs (line 520) | func TestMiddlewareFailsClosedWhenWAFPanicOccurs(t *testing.T) {
function TestEngineSnapshotTracksLifecycleCounters (line 545) | func TestEngineSnapshotTracksLifecycleCounters(t *testing.T) {
function TestEngineInitReplacesMetricsCollectorWhenProvided (line 567) | func TestEngineInitReplacesMetricsCollectorWhenProvided(t *testing.T) {
function TestEngineInitResetsMetricsCollectorToDefaultWhenOmitted (line 597) | func TestEngineInitResetsMetricsCollectorToDefaultWhenOmitted(t *testing...
function TestReloadWithoutDirectivesSucceeds (line 630) | func TestReloadWithoutDirectivesSucceeds(t *testing.T) {
function TestNewEngineWildcardDirectivesRequireMatch (line 654) | func TestNewEngineWildcardDirectivesRequireMatch(t *testing.T) {
function TestNewEngineQuestionMarkGlobDirectivesMatch (line 666) | func TestNewEngineQuestionMarkGlobDirectivesMatch(t *testing.T) {
function TestNewEngineCharacterClassGlobDirectivesMatch (line 682) | func TestNewEngineCharacterClassGlobDirectivesMatch(t *testing.T) {
function TestNewEngineWildcardDirectivesWithRootFSRequireMatch (line 698) | func TestNewEngineWildcardDirectivesWithRootFSRequireMatch(t *testing.T) {
function TestDefaultMetricsCollectorRecordLatencyUsesOnlineAverage (line 712) | func TestDefaultMetricsCollectorRecordLatencyUsesOnlineAverage(t *testin...
function newInstanceApp (line 731) | func newInstanceApp(engine *Engine, cfg MiddlewareConfig) *fiber.App {
function newTestEngine (line 740) | func newTestEngine(t *testing.T) (*Engine, error) {
function newTestEngineWithRules (line 745) | func newTestEngineWithRules(t *testing.T, rules string) (*Engine, error) {
function writeRuleFile (line 756) | func writeRuleFile(t *testing.T, dir, name, contents string) string {
function performRequest (line 767) | func performRequest(t *testing.T, app *fiber.App, req *http.Request) *ht...
type nilSnapshotCollector (line 778) | type nilSnapshotCollector struct
method RecordRequest (line 780) | func (nilSnapshotCollector) RecordRequest() {}
method RecordBlock (line 781) | func (nilSnapshotCollector) RecordBlock() {}
method RecordLatency (line 782) | func (nilSnapshotCollector) RecordLatency(time.Duration) {}
method GetMetrics (line 783) | func (nilSnapshotCollector) GetMetrics() *MetricsSnapshot { return nil }
method Reset (line 784) | func (nilSnapshotCollector) Reset() {}
type nilPtrSnapshotCollector (line 786) | type nilPtrSnapshotCollector struct
method RecordRequest (line 788) | func (*nilPtrSnapshotCollector) RecordRequest() {}
method RecordBlock (line 789) | func (*nilPtrSnapshotCollector) RecordBlock() {}
method RecordLatency (line 790) | func (*nilPtrSnapshotCollector) RecordLatency(time.Duration) {}
method GetMetrics (line 791) | func (*nilPtrSnapshotCollector) GetMetrics() *MetricsSnapshot { return...
method Reset (line 792) | func (*nilPtrSnapshotCollector) Reset() {}
type countingCollector (line 794) | type countingCollector struct
method RecordRequest (line 799) | func (c *countingCollector) RecordRequest() { c.requests++ }
method RecordBlock (line 800) | func (c *countingCollector) RecordBlock() { c.blocks++ }
method RecordLatency (line 801) | func (c *countingCollector) RecordLatency(time.Duration) {}
method GetMetrics (line 802) | func (c *countingCollector) GetMetrics() *MetricsSnapshot {
method Reset (line 809) | func (c *countingCollector) Reset() {
type fakePanicWAF (line 814) | type fakePanicWAF struct
method NewTransaction (line 816) | func (fakePanicWAF) NewTransaction() types.Transaction {
method NewTransactionWithID (line 820) | func (fakePanicWAF) NewTransactionWithID(string) types.Transaction {
type fakePanicTransaction (line 824) | type fakePanicTransaction struct
method ProcessConnection (line 826) | func (fakePanicTransaction) ProcessConnection(string, int, string, int...
method ProcessURI (line 827) | func (fakePanicTransaction) ProcessURI(string, string, string) ...
method SetServerName (line 828) | func (fakePanicTransaction) SetServerName(string) ...
method AddRequestHeader (line 829) | func (fakePanicTransaction) AddRequestHeader(string, string) ...
method ProcessRequestHeaders (line 830) | func (fakePanicTransaction) ProcessRequestHeaders() *types.Interruptio...
method RequestBodyReader (line 831) | func (fakePanicTransaction) RequestBodyReader() (io.Reader, error) ...
method AddGetRequestArgument (line 832) | func (fakePanicTransaction) AddGetRequestArgument(string, string) ...
method AddPostRequestArgument (line 833) | func (fakePanicTransaction) AddPostRequestArgument(string, string) ...
method AddPathRequestArgument (line 834) | func (fakePanicTransaction) AddPathRequestArgument(string, string) ...
method AddResponseArgument (line 835) | func (fakePanicTransaction) AddResponseArgument(string, string) ...
method ProcessRequestBody (line 836) | func (fakePanicTransaction) ProcessRequestBody() (*types.Interruption,...
method WriteRequestBody (line 837) | func (fakePanicTransaction) WriteRequestBody([]byte) (*types.Interrupt...
method ReadRequestBodyFrom (line 840) | func (fakePanicTransaction) ReadRequestBodyFrom(io.Reader) (*types.Int...
method AddResponseHeader (line 843) | func (fakePanicTransaction) AddResponseHeader(string, string) {}
method ProcessResponseHeaders (line 844) | func (fakePanicTransaction) ProcessResponseHeaders(int, string) *types...
method ResponseBodyReader (line 847) | func (fakePanicTransaction) ResponseBodyReader() (io.Reader, error) { ...
method ProcessResponseBody (line 848) | func (fakePanicTransaction) ProcessResponseBody() (*types.Interruption...
method WriteResponseBody (line 851) | func (fakePanicTransaction) WriteResponseBody([]byte) (*types.Interrup...
method ReadResponseBodyFrom (line 854) | func (fakePanicTransaction) ReadResponseBodyFrom(io.Reader) (*types.In...
method ProcessLogging (line 857) | func (fakePanicTransaction) ProcessLogging() {}
method IsRuleEngineOff (line 858) | func (fakePanicTransaction) IsRuleEngineOff() bool { return f...
method IsRequestBodyAccessible (line 859) | func (fakePanicTransaction) IsRequestBodyAccessible() bool { return f...
method IsResponseBodyAccessible (line 860) | func (fakePanicTransaction) IsResponseBodyAccessible() bool { return f...
method IsResponseBodyProcessable (line 861) | func (fakePanicTransaction) IsResponseBodyProcessable() bool {
method IsInterrupted (line 864) | func (fakePanicTransaction) IsInterrupted() bool { retur...
method Interruption (line 865) | func (fakePanicTransaction) Interruption() *types.Interruption { retur...
method MatchedRules (line 866) | func (fakePanicTransaction) MatchedRules() []types.MatchedRule { retur...
method DebugLogger (line 867) | func (fakePanicTransaction) DebugLogger() debuglog.Logger { retur...
method ID (line 868) | func (fakePanicTransaction) ID() string { retur...
method Close (line 869) | func (fakePanicTransaction) Close() error { retur...
FILE: v3/coraza/metrics.go
type MetricsCollector (line 11) | type MetricsCollector interface
type MetricsSnapshot (line 20) | type MetricsSnapshot struct
type EngineSnapshot (line 34) | type EngineSnapshot struct
type MetricsReport (line 60) | type MetricsReport struct
type defaultMetricsCollector (line 67) | type defaultMetricsCollector struct
method RecordRequest (line 81) | func (m *defaultMetricsCollector) RecordRequest() {
method RecordBlock (line 85) | func (m *defaultMetricsCollector) RecordBlock() {
method RecordLatency (line 89) | func (m *defaultMetricsCollector) RecordLatency(duration time.Duration) {
method GetMetrics (line 102) | func (m *defaultMetricsCollector) GetMetrics() *MetricsSnapshot {
method Reset (line 127) | func (m *defaultMetricsCollector) Reset() {
function NewDefaultMetricsCollector (line 77) | func NewDefaultMetricsCollector() MetricsCollector {
method MetricsSnapshot (line 138) | func (e *Engine) MetricsSnapshot() MetricsSnapshot {
method Snapshot (line 153) | func (e *Engine) Snapshot() EngineSnapshot {
method Report (line 158) | func (e *Engine) Report() MetricsReport {
FILE: v3/fgprof/config.go
type Config (line 5) | type Config struct
function configDefault (line 23) | func configDefault(config ...Config) Config {
FILE: v3/fgprof/fgprof.go
function New (line 9) | func New(conf ...Config) fiber.Handler {
FILE: v3/fgprof/fgprof_test.go
function Test_Non_Fgprof_Path (line 15) | func Test_Non_Fgprof_Path(t *testing.T) {
function Test_Non_Fgprof_Path_WithPrefix (line 33) | func Test_Non_Fgprof_Path_WithPrefix(t *testing.T) {
function Test_Fgprof_Path (line 53) | func Test_Fgprof_Path(t *testing.T) {
function Test_Fgprof_Path_WithPrefix (line 67) | func Test_Fgprof_Path_WithPrefix(t *testing.T) {
function Test_Fgprof_Next (line 93) | func Test_Fgprof_Next(t *testing.T) {
function Test_Fgprof_Next_WithPrefix (line 108) | func Test_Fgprof_Next_WithPrefix(t *testing.T) {
FILE: v3/hcaptcha/config.go
constant DefaultSiteVerifyURL (line 13) | DefaultSiteVerifyURL = "https://api.hcaptcha.com/siteverify"
type Config (line 16) | type Config struct
function DefaultResponseKeyFunc (line 33) | func DefaultResponseKeyFunc(c fiber.Ctx) (string, error) {
FILE: v3/hcaptcha/hcaptcha.go
type HCaptcha (line 16) | type HCaptcha struct
method Validate (line 37) | func (h *HCaptcha) Validate(c fiber.Ctx) error {
function New (line 21) | func New(config Config) fiber.Handler {
FILE: v3/hcaptcha/hcaptcha_test.go
constant TestSecretKey (line 17) | TestSecretKey = "0x0000000000000000000000000000000000000000"
constant TestResponseToken (line 18) | TestResponseToken = "20000000-aaaa-bbbb-cccc-000000000002"
function newSiteVerifyServer (line 21) | func newSiteVerifyServer(t *testing.T, success bool) *httptest.Server {
function TestHCaptchaDefaultValidation (line 36) | func TestHCaptchaDefaultValidation(t *testing.T) {
function TestHCaptchaValidateFunc (line 95) | func TestHCaptchaValidateFunc(t *testing.T) {
function TestDefaultResponseKeyFunc (line 194) | func TestDefaultResponseKeyFunc(t *testing.T) {
FILE: v3/i18n/config.go
type Config (line 14) | type Config struct
type Loader (line 54) | type Loader interface
type LoaderFunc (line 58) | type LoaderFunc
method LoadMessage (line 60) | func (f LoaderFunc) LoadMessage(path string) ([]byte, error) {
function defaultLangHandler (line 74) | func defaultLangHandler(c fiber.Ctx, defaultLang string) string {
function configDefault (line 88) | func configDefault(config ...*Config) *Config {
FILE: v3/i18n/embed.go
type EmbedLoader (line 7) | type EmbedLoader struct
method LoadMessage (line 11) | func (e *EmbedLoader) LoadMessage(path string) ([]byte, error) {
FILE: v3/i18n/embed_test.go
function newEmbedServer (line 21) | func newEmbedServer() *fiber.App {
function request (line 45) | func request(lang language.Tag, name string) (*http.Response, error) {
function TestEmbedLoader_LoadMessage (line 56) | func TestEmbedLoader_LoadMessage(t *testing.T) {
FILE: v3/i18n/example/main.go
function main (line 12) | func main() {
FILE: v3/i18n/i18n.go
type I18n (line 17) | type I18n struct
method MustLocalize (line 94) | func (i *I18n) MustLocalize(ctx fiber.Ctx, params interface{}) string {
method Localize (line 115) | func (i *I18n) Localize(ctx fiber.Ctx, params interface{}) (string, er...
function New (line 22) | func New(config ...*Config) *I18n {
function prepareConfig (line 28) | func prepareConfig(config ...*Config) *Config {
method loadMessage (line 47) | func (c *Config) loadMessage(filepath string) {
method loadMessages (line 57) | func (c *Config) loadMessages() *Config {
method initLocalizerMap (line 66) | func (c *Config) initLocalizerMap() {
FILE: v3/i18n/i18n_test.go
function newServer (line 18) | func newServer(translator *I18n) *fiber.App {
function makeRequest (line 39) | func makeRequest(lang language.Tag, name string, app *fiber.App) (*http....
function TestI18nEN (line 52) | func TestI18nEN(t *testing.T) {
function TestI18nZH (line 94) | func TestI18nZH(t *testing.T) {
function TestParallelI18n (line 135) | func TestParallelI18n(t *testing.T) {
function TestTranslatorConcurrentLocalize (line 185) | func TestTranslatorConcurrentLocalize(t *testing.T) {
function TestLocalize (line 231) | func TestLocalize(t *testing.T) {
function TestNew_doesNotMutateCallerConfig (line 270) | func TestNew_doesNotMutateCallerConfig(t *testing.T) {
function TestLocalize_nilReceiver (line 287) | func TestLocalize_nilReceiver(t *testing.T) {
function TestLocalize_unsupportedParamsType (line 295) | func TestLocalize_unsupportedParamsType(t *testing.T) {
function TestLocalize_nilLocalizeConfig (line 312) | func TestLocalize_nilLocalizeConfig(t *testing.T) {
function TestMustLocalize_panics (line 329) | func TestMustLocalize_panics(t *testing.T) {
function Test_defaultLangHandler (line 343) | func Test_defaultLangHandler(t *testing.T) {
FILE: v3/jwt/config.go
type Config (line 25) | type Config struct
type SigningKey (line 79) | type SigningKey struct
function makeCfg (line 92) | func makeCfg(config []Config) (cfg Config) {
function multiKeyfunc (line 168) | func multiKeyfunc(givenKeys map[string]keyfunc.GivenKey, jwkSetURLs []st...
function keyfuncOptions (line 184) | func keyfuncOptions(givenKeys map[string]keyfunc.GivenKey) keyfunc.Optio...
function signingKeyFunc (line 197) | func signingKeyFunc(key SigningKey) jwt.Keyfunc {
FILE: v3/jwt/config_test.go
function TestPanicOnMissingConfiguration (line 13) | func TestPanicOnMissingConfiguration(t *testing.T) {
function TestDefaultConfiguration (line 30) | func TestDefaultConfiguration(t *testing.T) {
function TestCustomExtractor (line 48) | func TestCustomExtractor(t *testing.T) {
function TestPanicOnInvalidSigningKey (line 67) | func TestPanicOnInvalidSigningKey(t *testing.T) {
function TestPanicOnInvalidSigningKeys (line 75) | func TestPanicOnInvalidSigningKeys(t *testing.T) {
function TestPanicOnInvalidJWKSetURLs (line 85) | func TestPanicOnInvalidJWKSetURLs(t *testing.T) {
function TestCustomClaims (line 94) | func TestCustomClaims(t *testing.T) {
function TestTokenProcessorFunc_Configured (line 116) | func TestTokenProcessorFunc_Configured(t *testing.T) {
function TestPanicOnUnsupportedJWKSetURLScheme (line 138) | func TestPanicOnUnsupportedJWKSetURLScheme(t *testing.T) {
FILE: v3/jwt/crypto.go
constant HS256 (line 5) | HS256 = "HS256"
constant HS384 (line 8) | HS384 = "HS384"
constant HS512 (line 11) | HS512 = "HS512"
constant ES256 (line 14) | ES256 = "ES256"
constant ES384 (line 17) | ES384 = "ES384"
constant ES512 (line 20) | ES512 = "ES512"
constant P256 (line 23) | P256 = "P-256"
constant P384 (line 26) | P384 = "P-384"
constant P521 (line 29) | P521 = "P-521"
constant RS256 (line 32) | RS256 = "RS256"
constant RS384 (line 35) | RS384 = "RS384"
constant RS512 (line 38) | RS512 = "RS512"
constant PS256 (line 41) | PS256 = "PS256"
constant PS384 (line 44) | PS384 = "PS384"
constant PS512 (line 47) | PS512 = "PS512"
FILE: v3/jwt/jwt.go
type contextKey (line 17) | type contextKey
constant tokenKey (line 21) | tokenKey contextKey = iota
function New (line 25) | func New(config ...Config) fiber.Handler {
function FromContext (line 66) | func FromContext(ctx any) *jwt.Token {
FILE: v3/jwt/jwt_test.go
type TestToken (line 22) | type TestToken struct
constant defaultSigningKey (line 75) | defaultSigningKey = "secret"
constant defaultKeySet (line 76) | defaultKeySet = `
function TestJwtTokenProcessorFunc (line 111) | func TestJwtTokenProcessorFunc(t *testing.T) {
function TestJwtFromHeader (line 152) | func TestJwtFromHeader(t *testing.T) {
function TestJwtFromCookie (line 248) | func TestJwtFromCookie(t *testing.T) {
function TestJwkFromServer (line 292) | func TestJwkFromServer(t *testing.T) {
function TestJwkFromServers (line 346) | func TestJwkFromServers(t *testing.T) {
function TestCustomKeyfunc (line 406) | func TestCustomKeyfunc(t *testing.T) {
function TestMultiKeys (line 439) | func TestMultiKeys(t *testing.T) {
function customKeyfunc (line 498) | func customKeyfunc() jwt.Keyfunc {
function TestFromContext (line 509) | func TestFromContext(t *testing.T) {
function TestCustomErrorHandler (line 550) | func TestCustomErrorHandler(t *testing.T) {
function TestCustomSuccessHandler (line 583) | func TestCustomSuccessHandler(t *testing.T) {
function TestNextFunction (line 620) | func TestNextFunction(t *testing.T) {
function TestInvalidSigningKey (line 657) | func TestInvalidSigningKey(t *testing.T) {
function TestFromContextWithoutToken (line 668) | func TestFromContextWithoutToken(t *testing.T) {
function TestMalformedToken (line 692) | func TestMalformedToken(t *testing.T) {
function TestTokenProcessorFuncError (line 717) | func TestTokenProcessorFuncError(t *testing.T) {
function TestFromContext_PassLocalsToContext (line 745) | func TestFromContext_PassLocalsToContext(t *testing.T) {
FILE: v3/loadshed/cpu.go
type LoadCriteria (line 16) | type LoadCriteria interface
type CPULoadCriteria (line 22) | type CPULoadCriteria struct
method startSampler (line 38) | func (c *CPULoadCriteria) startSampler() {
method sample (line 94) | func (c *CPULoadCriteria) sample(ctx context.Context, interval time.Du...
method Stop (line 127) | func (c *CPULoadCriteria) Stop() {
method Metric (line 144) | func (c *CPULoadCriteria) Metric(ctx context.Context) (float64, error) {
method ShouldShed (line 163) | func (c *CPULoadCriteria) ShouldShed(metric float64) bool {
constant minSamplerSleep (line 36) | minSamplerSleep = 100 * time.Millisecond
type CPUPercentGetter (line 174) | type CPUPercentGetter interface
type DefaultCPUPercentGetter (line 178) | type DefaultCPUPercentGetter struct
method PercentWithContext (line 180) | func (*DefaultCPUPercentGetter) PercentWithContext(ctx context.Context...
FILE: v3/loadshed/loadshed.go
type Config (line 9) | type Config struct
function configWithDefaults (line 34) | func configWithDefaults(config ...Config) Config {
function New (line 78) | func New(config ...Config) fiber.Handler {
FILE: v3/loadshed/loadshed_test.go
function waitForSample (line 22) | func waitForSample(t *testing.T, criteria *CPULoadCriteria) {
type MockCPUPercentGetter (line 45) | type MockCPUPercentGetter struct
method PercentWithContext (line 49) | func (m *MockCPUPercentGetter) PercentWithContext(_ context.Context, _...
type PanickingGetter (line 54) | type PanickingGetter struct
method PercentWithContext (line 56) | func (*PanickingGetter) PercentWithContext(_ context.Context, _ time.D...
type ErrorGetter (line 61) | type ErrorGetter struct
method PercentWithContext (line 63) | func (*ErrorGetter) PercentWithContext(_ context.Context, _ time.Durat...
type EmptyGetter (line 68) | type EmptyGetter struct
method PercentWithContext (line 70) | func (*EmptyGetter) PercentWithContext(_ context.Context, _ time.Durat...
function ReturnOK (line 74) | func ReturnOK(c fiber.Ctx) error {
function Test_Loadshed_LowerThreshold (line 78) | func Test_Loadshed_LowerThreshold(t *testing.T) {
function Test_Loadshed_DefaultCriteriaWhenNil (line 104) | func Test_Loadshed_DefaultCriteriaWhenNil(t *testing.T) {
function Test_Loadshed_DefaultCriteriaNoArgs (line 118) | func Test_Loadshed_DefaultCriteriaNoArgs(t *testing.T) {
function Test_Loadshed_MiddleValue (line 132) | func Test_Loadshed_MiddleValue(t *testing.T) {
function Test_Loadshed_UpperThreshold (line 170) | func Test_Loadshed_UpperThreshold(t *testing.T) {
function Test_Loadshed_CustomOnShed (line 192) | func Test_Loadshed_CustomOnShed(t *testing.T) {
function Test_Loadshed_CustomOnShedWithResponse (line 218) | func Test_Loadshed_CustomOnShedWithResponse(t *testing.T) {
function Test_Loadshed_CustomOnShedWithNilReturn (line 247) | func Test_Loadshed_CustomOnShedWithNilReturn(t *testing.T) {
function Test_Loadshed_CustomOnShedWithCustomError (line 275) | func Test_Loadshed_CustomOnShedWithCustomError(t *testing.T) {
function Test_Loadshed_CustomOnShedWithResponseAndCustomError (line 303) | func Test_Loadshed_CustomOnShedWithResponseAndCustomError(t *testing.T) {
function Test_Loadshed_CustomOnShedWithJSON (line 344) | func Test_Loadshed_CustomOnShedWithJSON(t *testing.T) {
function Test_Loadshed_TypedNilCriteria (line 376) | func Test_Loadshed_TypedNilCriteria(t *testing.T) {
function Test_CPULoadCriteria_StopBeforeStart (line 393) | func Test_CPULoadCriteria_StopBeforeStart(t *testing.T) {
function Test_CPULoadCriteria_PanickingGetter (line 412) | func Test_CPULoadCriteria_PanickingGetter(t *testing.T) {
function Test_CPULoadCriteria_ErrorGetter (line 433) | func Test_CPULoadCriteria_ErrorGetter(t *testing.T) {
function Test_CPULoadCriteria_EmptyGetter (line 451) | func Test_CPULoadCriteria_EmptyGetter(t *testing.T) {
function Test_CPULoadCriteria_MetricCancelledContext (line 469) | func Test_CPULoadCriteria_MetricCancelledContext(t *testing.T) {
FILE: v3/monitor/config.go
type Config (line 10) | type Config struct
function configDefault (line 66) | func configDefault(config ...Config) Config {
FILE: v3/monitor/config_test.go
function Test_Config_Default (line 11) | func Test_Config_Default(t *testing.T) {
FILE: v3/monitor/index.go
type viewBag (line 10) | type viewBag struct
function newIndex (line 19) | func newIndex(dat viewBag) string {
constant defaultTitle (line 32) | defaultTitle = "Fiber Monitor"
constant defaultRefresh (line 34) | defaultRefresh = 3 * time.Second
constant timeoutDiff (line 35) | timeoutDiff = 200
constant minRefresh (line 36) | minRefresh = timeoutDiff * time.Millisecond
constant defaultFontURL (line 37) | defaultFontURL = `https://fonts.googleapis.com/css2?family=Roboto:wgh...
constant defaultChartJSURL (line 38) | defaultChartJSURL = `https://cdn.jsdelivr.net/npm/chart.js@2.9/dist/Char...
constant defaultCustomHead (line 39) | defaultCustomHead = ``
constant indexHTML (line 42) | indexHTML = `<!DOCTYPE html>
FILE: v3/monitor/monitor.go
type stats (line 18) | type stats struct
type statsPID (line 23) | type statsPID struct
type statsOS (line 29) | type statsOS struct
function New (line 56) | func New(config ...Config) fiber.Handler {
function updateStatistics (line 105) | func updateStatistics(p *process.Process, numcpu int) {
FILE: v3/monitor/monitor_test.go
function Test_Monitor_405 (line 17) | func Test_Monitor_405(t *testing.T) {
function Test_Monitor_Html (line 29) | func Test_Monitor_Html(t *testing.T) {
function Test_Monitor_Html_CustomCodes (line 66) | func Test_Monitor_Html_CustomCodes(t *testing.T) {
function Test_Monitor_JSON (line 114) | func Test_Monitor_JSON(t *testing.T) {
function Benchmark_Monitor (line 135) | func Benchmark_Monitor(b *testing.B) {
function Test_Monitor_Next (line 163) | func Test_Monitor_Next(t *testing.T) {
function Test_Monitor_APIOnly (line 180) | func Test_Monitor_APIOnly(t *testing.T) {
FILE: v3/newrelic/fiber.go
type contextKey (line 18) | type contextKey
constant transactionKey (line 21) | transactionKey contextKey = iota
type Config (line 24) | type Config struct
function New (line 59) | func New(cfg Config) fiber.Handler {
function FromContext (line 124) | func FromContext(ctx any) *newrelic.Transaction {
function createTransactionName (line 136) | func createTransactionName(c fiber.Ctx) string {
function createWebRequest (line 140) | func createWebRequest(c fiber.Ctx, host, method, scheme string, filter f...
function transport (line 167) | func transport(schema string) newrelic.TransportType {
function DefaultErrorStatusCodeHandler (line 179) | func DefaultErrorStatusCodeHandler(c fiber.Ctx, err error) int {
FILE: v3/newrelic/fiber_test.go
function TestNewRelicAppConfig (line 15) | func TestNewRelicAppConfig(t *testing.T) {
function TestDefaultErrorStatusCodeHandler (line 355) | func TestDefaultErrorStatusCodeHandler(t *testing.T) {
function TestFromContext (line 394) | func TestFromContext(t *testing.T) {
function TestCreateWebRequest (line 426) | func TestCreateWebRequest(t *testing.T) {
function TestFromContext_PassLocalsToContext (line 469) | func TestFromContext_PassLocalsToContext(t *testing.T) {
FILE: v3/opa/fiber.go
type InputCreationFunc (line 13) | type InputCreationFunc
type Config (line 15) | type Config struct
method fillAndValidate (line 80) | func (c *Config) fillAndValidate() error {
function New (line 25) | func New(cfg Config) fiber.Handler {
function defaultInput (line 100) | func defaultInput(ctx fiber.Ctx) (map[string]interface{}, error) {
FILE: v3/opa/fiber_test.go
function TestPanicWhenRegoQueryEmpty (line 15) | func TestPanicWhenRegoQueryEmpty(t *testing.T) {
function TestDefaultDeniedStatusCode400WhenConfigDeniedStatusCodeEmpty (line 23) | func TestDefaultDeniedStatusCode400WhenConfigDeniedStatusCodeEmpty(t *te...
function TestOpaNotAllowedRegoPolicyShouldReturnConfigDeniedStatusCode (line 54) | func TestOpaNotAllowedRegoPolicyShouldReturnConfigDeniedStatusCode(t *te...
function TestOpaRequestMethodRegoPolicyShouldReturnConfigDeniedStatusCode (line 86) | func TestOpaRequestMethodRegoPolicyShouldReturnConfigDeniedStatusCode(t ...
function TestOpaRequestPathRegoPolicyShouldReturnOK (line 120) | func TestOpaRequestPathRegoPolicyShouldReturnOK(t *testing.T) {
function TestOpaQueryStringRegoPolicyShouldReturnOK (line 154) | func TestOpaQueryStringRegoPolicyShouldReturnOK(t *testing.T) {
function TestOpaRequestHeadersRegoPolicyShouldReturnOK (line 191) | func TestOpaRequestHeadersRegoPolicyShouldReturnOK(t *testing.T) {
function TestOpaRequestWithCustomInput (line 230) | func TestOpaRequestWithCustomInput(t *testing.T) {
function TestOpaRequestWithCustomInputError (line 270) | func TestOpaRequestWithCustomInputError(t *testing.T) {
function TestFillAndValidate (line 308) | func TestFillAndValidate(t *testing.T) {
FILE: v3/otel/config.go
type config (line 12) | type config struct
type Option (line 26) | type Option interface
type optionFunc (line 30) | type optionFunc
method apply (line 32) | func (o optionFunc) apply(c *config) {
function WithNext (line 38) | func WithNext(f func(ctx fiber.Ctx) bool) Option {
function WithPropagators (line 47) | func WithPropagators(propagators propagation.TextMapPropagator) Option {
function WithTracerProvider (line 55) | func WithTracerProvider(provider oteltrace.TracerProvider) Option {
function WithMeterProvider (line 63) | func WithMeterProvider(provider otelmetric.MeterProvider) Option {
function WithSpanNameFormatter (line 71) | func WithSpanNameFormatter(f func(ctx fiber.Ctx) string) Option {
function WithPort (line 80) | func WithPort(port int) Option {
function WithCustomAttributes (line 88) | func WithCustomAttributes(f func(ctx fiber.Ctx) []attribute.KeyValue) Op...
function WithCustomMetricAttributes (line 96) | func WithCustomMetricAttributes(f func(ctx fiber.Ctx) []attribute.KeyVal...
function WithClientIP (line 104) | func WithClientIP(collect bool) Option {
function WithCollectClientIP (line 112) | func WithCollectClientIP(collect bool) Option {
function WithoutMetrics (line 117) | func WithoutMetrics(withoutMetrics bool) Option {
FILE: v3/otel/example/server.go
function main (line 26) | func main() {
function initTracer (line 56) | func initTracer() *sdktrace.TracerProvider {
function getUser (line 76) | func getUser(ctx context.Context, id string) string {
FILE: v3/otel/fiber.go
constant tracerKey (line 26) | tracerKey = "gofiber-contrib-tracer-fiber"
constant instrumentationName (line 27) | instrumentationName = "github.com/gofiber/contrib/v3/otel"
constant MetricNameHTTPServerRequestDuration (line 29) | MetricNameHTTPServerRequestDuration = "http.server.request.duration"
constant MetricNameHTTPServerRequestBodySize (line 30) | MetricNameHTTPServerRequestBodySize = "http.server.request.body.size"
constant MetricNameHTTPServerResponseBodySize (line 31) | MetricNameHTTPServerResponseBodySize = "http.server.response.body.size"
constant MetricNameHTTPServerActiveRequests (line 32) | MetricNameHTTPServerActiveRequests = "http.server.active_requests"
constant UnitDimensionless (line 35) | UnitDimensionless = "1"
constant UnitBytes (line 36) | UnitBytes = "By"
constant UnitSeconds (line 37) | UnitSeconds = "s"
constant MetricNameHttpServerDuration (line 40) | MetricNameHttpServerDuration = MetricNameHTTPServerRequestDuration
constant MetricNameHttpServerRequestSize (line 42) | MetricNameHttpServerRequestSize = MetricNameHTTPServerRequestBodySize
constant MetricNameHttpServerResponseSize (line 44) | MetricNameHttpServerResponseSize = MetricNameHTTPServerResponseBodySize
constant MetricNameHttpServerActiveRequests (line 46) | MetricNameHttpServerActiveRequests = MetricNameHTTPServerActiveRequests
constant UnitMilliseconds (line 49) | UnitMilliseconds = "ms"
type bodyStreamSizeReader (line 52) | type bodyStreamSizeReader struct
method Read (line 59) | func (b *bodyStreamSizeReader) Read(p []byte) (n int, err error) {
method Close (line 74) | func (b *bodyStreamSizeReader) Close() error {
function detachedMetricContext (line 83) | func detachedMetricContext(ctx context.Context) context.Context {
function Middleware (line 98) | func Middleware(opts ...Option) fiber.Handler {
function defaultSpanNameFormatter (line 292) | func defaultSpanNameFormatter(ctx fiber.Ctx) string {
FILE: v3/otel/fiber_context_test.go
function TestMiddleware_StoreTracerInContextWithPassLocalsToContext (line 13) | func TestMiddleware_StoreTracerInContextWithPassLocalsToContext(t *testi...
FILE: v3/otel/internal/http.go
function SpanStatusFromHTTPStatusCodeAndSpanKind (line 14) | func SpanStatusFromHTTPStatusCodeAndSpanKind(code int, spanKind trace.Sp...
function isCode4xx (line 27) | func isCode4xx(code int) bool {
FILE: v3/otel/internal/http_test.go
function TestIsCode4xxIsNotValid (line 11) | func TestIsCode4xxIsNotValid(t *testing.T) {
function TestIsCode4xxIsValid (line 17) | func TestIsCode4xxIsValid(t *testing.T) {
function TestStatusErrorWithMessage (line 23) | func TestStatusErrorWithMessage(t *testing.T) {
function TestStatusErrorWithMessageForIgnoredHTTPCode (line 30) | func TestStatusErrorWithMessageForIgnoredHTTPCode(t *testing.T) {
function TestStatusErrorWhenHTTPCode5xx (line 37) | func TestStatusErrorWhenHTTPCode5xx(t *testing.T) {
function TestStatusUnsetWhenServerSpanAndBadRequest (line 44) | func TestStatusUnsetWhenServerSpanAndBadRequest(t *testing.T) {
function TestStatusUnset (line 51) | func TestStatusUnset(t *testing.T) {
FILE: v3/otel/otel_test/fiber_test.go
constant instrumentationName (line 34) | instrumentationName = "github.com/gofiber/contrib/v3/otel"
function TestChildSpanFromGlobalTracer (line 36) | func TestChildSpanFromGlobalTracer(t *testing.T) {
function TestChildSpanFromCustomTracer (line 55) | func TestChildSpanFromCustomTracer(t *testing.T) {
function TestSkipWithNext (line 74) | func TestSkipWithNext(t *testing.T) {
function TestTrace200 (line 96) | func TestTrace200(t *testing.T) {
function TestError (line 134) | func TestError(t *testing.T) {
function TestErrorOnlyHandledOnce (line 164) | func TestErrorOnlyHandledOnce(t *testing.T) {
function TestGetSpanNotInstrumented (line 183) | func TestGetSpanNotInstrumented(t *testing.T) {
function TestPropagationWithGlobalPropagators (line 201) | func TestPropagationWithGlobalPropagators(t *testing.T) {
function TestPropagationWithCustomPropagators (line 232) | func TestPropagationWithCustomPropagators(t *testing.T) {
function TestHasBasicAuth (line 261) | func TestHasBasicAuth(t *testing.T) {
function TestMetric (line 297) | func TestMetric(t *testing.T) {
function assertScopeMetrics (line 341) | func assertScopeMetrics(t *testing.T, sm metricdata.ScopeMetrics, route ...
function getHistogram (line 393) | func getHistogram(value float64, attrs []attribute.KeyValue) metricdata....
function TestCustomAttributes (line 427) | func TestCustomAttributes(t *testing.T) {
function TestCustomMetricAttributes (line 471) | func TestCustomMetricAttributes(t *testing.T) {
function TestOutboundTracingPropagation (line 523) | func TestOutboundTracingPropagation(t *testing.T) {
function TestOutboundTracingPropagationWithInboundContext (line 546) | func TestOutboundTracingPropagationWithInboundContext(t *testing.T) {
function TestCollectClientIP (line 577) | func TestCollectClientIP(t *testing.T) {
function TestMiddlewarePreservesUserContext (line 631) | func TestMiddlewarePreservesUserContext(t *testing.T) {
function TestWithoutMetrics (line 664) | func TestWithoutMetrics(t *testing.T) {
function TestWithoutMetricsWithStreamResponse (line 694) | func TestWithoutMetricsWithStreamResponse(t *testing.T) {
function TestResponseBodySizeWithStream (line 724) | func TestResponseBodySizeWithStream(t *testing.T) {
FILE: v3/otel/semconv.go
function httpServerMetricAttributesFromRequest (line 20) | func httpServerMetricAttributesFromRequest(c fiber.Ctx, cfg config) []at...
function httpServerTraceAttributesFromRequest (line 40) | func httpServerTraceAttributesFromRequest(c fiber.Ctx, cfg config) []att...
function httpNetworkProtocolAttributes (line 80) | func httpNetworkProtocolAttributes(c fiber.Ctx) []attribute.KeyValue {
function requestScheme (line 88) | func requestScheme(c fiber.Ctx) string {
function HasBasicAuth (line 97) | func HasBasicAuth(auth string) (string, bool) {
FILE: v3/paseto/config.go
type Config (line 18) | type Config struct
function defaultErrorHandler (line 70) | func defaultErrorHandler(c fiber.Ctx, err error) error {
function defaultValidateFunc (line 79) | func defaultValidateFunc(data []byte) (interface{}, error) {
function configDefault (line 99) | func configDefault(authConfigs ...Config) Config {
FILE: v3/paseto/config_test.go
function assertRecoveryPanic (line 12) | func assertRecoveryPanic(t *testing.T) {
function Test_Config_No_SymmetricKey (line 17) | func Test_Config_No_SymmetricKey(t *testing.T) {
function Test_Config_Invalid_SymmetricKey (line 24) | func Test_Config_Invalid_SymmetricKey(t *testing.T) {
function Test_ConfigDefault (line 31) | func Test_ConfigDefault(t *testing.T) {
function Test_ConfigCustomLookup (line 44) | func Test_ConfigCustomLookup(t *testing.T) {
FILE: v3/paseto/helpers.go
type TokenPurpose (line 11) | type TokenPurpose
constant PurposeLocal (line 14) | PurposeLocal TokenPurpose = iota
constant PurposePublic (line 15) | PurposePublic
type PayloadValidator (line 27) | type PayloadValidator
type PayloadCreator (line 30) | type PayloadCreator
function CreateToken (line 35) | func CreateToken(key []byte, dataInfo string, duration time.Duration, pu...
FILE: v3/paseto/paseto.go
type contextKey (line 12) | type contextKey
constant payloadKey (line 16) | payloadKey contextKey = iota
function New (line 22) | func New(authConfigs ...Config) fiber.Handler {
function FromContext (line 67) | func FromContext(ctx any) interface{} {
FILE: v3/paseto/paseto_test.go
constant testMessage (line 20) | testMessage = "fiber with PASETO middleware!!"
constant invalidToken (line 21) | invalidToken = "We are gophers!"
constant durationTest (line 22) | durationTest = 10 * time.Minute
constant symmetricKey (line 23) | symmetricKey = "go+fiber=love;FiberWithPASETO<3!"
constant privateKeySeed (line 24) | privateKeySeed = "e9c67fe2433aa4110caf029eba70df2c822cad226b6300ead3dcae...
type customPayload (line 27) | type customPayload struct
function createCustomToken (line 33) | func createCustomToken(key []byte, dataInfo string, duration time.Durati...
function generateTokenRequest (line 49) | func generateTokenRequest(
function getPrivateKey (line 76) | func getPrivateKey() ed25519.PrivateKey {
function assertErrorHandler (line 82) | func assertErrorHandler(t *testing.T, toAssert error) fiber.ErrorHandler {
function Test_PASETO_LocalToken_MissingToken (line 91) | func Test_PASETO_LocalToken_MissingToken(t *testing.T) {
function Test_PASETO_PublicToken_MissingToken (line 104) | func Test_PASETO_PublicToken_MissingToken(t *testing.T) {
function Test_PASETO_LocalToken_ErrDataUnmarshal (line 122) | func Test_PASETO_LocalToken_ErrDataUnmarshal(t *testing.T) {
function Test_PASETO_PublicToken_ErrDataUnmarshal (line 137) | func Test_PASETO_PublicToken_ErrDataUnmarshal(t *testing.T) {
function Test_PASETO_LocalToken_ErrTokenExpired (line 156) | func Test_PASETO_LocalToken_ErrTokenExpired(t *testing.T) {
function Test_PASETO_PublicToken_ErrTokenExpired (line 171) | func Test_PASETO_PublicToken_ErrTokenExpired(t *testing.T) {
function Test_PASETO_LocalToken_Next (line 191) | func Test_PASETO_LocalToken_Next(t *testing.T) {
function Test_PASETO_PublicToken_Next (line 207) | func Test_PASETO_PublicToken_Next(t *testing.T) {
function Test_PASETO_LocalTokenDecrypt (line 227) | func Test_PASETO_LocalTokenDecrypt(t *testing.T) {
function Test_PASETO_PublicTokenVerify (line 245) | func Test_PASETO_PublicTokenVerify(t *testing.T) {
function Test_PASETO_LocalToken_IncorrectBearerToken (line 267) | func Test_PASETO_LocalToken_IncorrectBearerToken(t *testing.T) {
function Test_PASETO_PublicToken_IncorrectBearerToken (line 280) | func Test_PASETO_PublicToken_IncorrectBearerToken(t *testing.T) {
function Test_PASETO_LocalToken_InvalidToken (line 298) | func Test_PASETO_LocalToken_InvalidToken(t *testing.T) {
function Test_PASETO_PublicToken_InvalidToken (line 310) | func Test_PASETO_PublicToken_InvalidToken(t *testing.T) {
function Test_PASETO_LocalToken_CustomValidate (line 327) | func Test_PASETO_LocalToken_CustomValidate(t *testing.T) {
function Test_PASETO_PublicToken_CustomValidate (line 361) | func Test_PASETO_PublicToken_CustomValidate(t *testing.T) {
function Test_PASETO_CustomErrorHandler (line 400) | func Test_PASETO_CustomErrorHandler(t *testing.T) {
function Test_PASETO_CustomSuccessHandler (line 425) | func Test_PASETO_CustomSuccessHandler(t *testing.T) {
function Test_PASETO_InvalidSymmetricKey (line 454) | func Test_PASETO_InvalidSymmetricKey(t *testing.T) {
function Test_PASETO_MissingPublicKey (line 470) | func Test_PASETO_MissingPublicKey(t *testing.T) {
function Test_PASETO_MissingPrivateKey (line 488) | func Test_PASETO_MissingPrivateKey(t *testing.T) {
function Test_PASETO_BothKeysProvided (line 505) | func Test_PASETO_BothKeysProvided(t *testing.T) {
function Test_PASETO_FromContextWithoutToken (line 524) | func Test_PASETO_FromContextWithoutToken(t *testing.T) {
function Test_PASETO_CustomValidateError (line 542) | func Test_PASETO_CustomValidateError(t *testing.T) {
function Test_PASETO_FromContext_PassLocalsToContext (line 567) | func Test_PASETO_FromContext_PassLocalsToContext(t *testing.T) {
FILE: v3/paseto/payload.go
constant pasetoTokenAudience (line 11) | pasetoTokenAudience = "gofiber.gophers"
constant pasetoTokenSubject (line 12) | pasetoTokenSubject = "user-token"
constant pasetoTokenField (line 13) | pasetoTokenField = "data"
function NewPayload (line 17) | func NewPayload(userToken string, duration time.Duration) (*paseto.JSONT...
FILE: v3/sentry/config.go
type contextKey (line 7) | type contextKey
constant hubKey (line 10) | hubKey contextKey = iota
type Config (line 14) | type Config struct
function configDefault (line 40) | func configDefault(config ...Config) Config {
FILE: v3/sentry/sentry.go
function New (line 13) | func New(config ...Config) fiber.Handler {
function MustGetHubFromContext (line 58) | func MustGetHubFromContext(ctx any) *sentry.Hub {
function GetHubFromContext (line 69) | func GetHubFromContext(ctx any) *sentry.Hub {
FILE: v3/sentry/sentry_test.go
type testCase (line 15) | type testCase struct
function testCasesBeforeRegister (line 24) | func testCasesBeforeRegister(t *testing.T) []testCase {
function Test_Sentry (line 180) | func Test_Sentry(t *testing.T) {
function Test_GetHubFromContext_PassLocalsToContext (line 232) | func Test_GetHubFromContext_PassLocalsToContext(t *testing.T) {
FILE: v3/socketio/socketio.go
constant TextMessage (line 19) | TextMessage = 1
constant BinaryMessage (line 21) | BinaryMessage = 2
constant CloseMessage (line 25) | CloseMessage = 8
constant PingMessage (line 28) | PingMessage = 9
constant PongMessage (line 31) | PongMessage = 10
constant EventMessage (line 37) | EventMessage = "message"
constant EventPing (line 40) | EventPing = "ping"
constant EventPong (line 41) | EventPong = "pong"
constant EventDisconnect (line 46) | EventDisconnect = "disconnect"
constant EventConnect (line 48) | EventConnect = "connect"
constant EventClose (line 50) | EventClose = "close"
constant EventError (line 52) | EventError = "error"
type message (line 74) | type message struct
type EventPayload (line 86) | type EventPayload struct
type ws (line 103) | type ws interface
type Websocket (line 127) | type Websocket struct
method GetUUID (line 276) | func (kws *Websocket) GetUUID() string {
method SetUUID (line 282) | func (kws *Websocket) SetUUID(uuid string) error {
method SetAttribute (line 307) | func (kws *Websocket) SetAttribute(key string, attribute interface{}) {
method GetAttribute (line 314) | func (kws *Websocket) GetAttribute(key string) interface{} {
method GetIntAttribute (line 326) | func (kws *Websocket) GetIntAttribute(key string) int {
method GetStringAttribute (line 337) | func (kws *Websocket) GetStringAttribute(key string) string {
method EmitToList (line 348) | func (kws *Websocket) EmitToList(uuids []string, message []byte, mType...
method EmitTo (line 366) | func (kws *Websocket) EmitTo(uuid string, message []byte, mType ...int...
method Broadcast (line 398) | func (kws *Websocket) Broadcast(message []byte, except bool, mType ......
method Fire (line 418) | func (kws *Websocket) Fire(event string, data []byte) {
method Emit (line 428) | func (kws *Websocket) Emit(message []byte, mType ...int) {
method Close (line 437) | func (kws *Websocket) Close() {
method IsAlive (line 442) | func (kws *Websocket) IsAlive() bool {
method hasConn (line 448) | func (kws *Websocket) hasConn() bool {
method setAlive (line 454) | func (kws *Websocket) setAlive(alive bool) {
method queueLength (line 461) | func (kws *Websocket) queueLength() int {
method pong (line 468) | func (kws *Websocket) pong(ctx context.Context) {
method write (line 482) | func (kws *Websocket) write(messageType int, messageBytes []byte) {
method send (line 491) | func (kws *Websocket) send(ctx context.Context) {
method run (line 523) | func (kws *Websocket) run() {
method read (line 537) | func (kws *Websocket) read(ctx context.Context) {
method disconnected (line 580) | func (kws *Websocket) disconnected(err error) {
method createUUID (line 602) | func (kws *Websocket) createUUID() string {
method randomUUID (line 607) | func (kws *Websocket) randomUUID() string {
method fireEvent (line 620) | func (kws *Websocket) fireEvent(event string, data []byte, error error) {
type safePool (line 153) | type safePool struct
method set (line 164) | func (p *safePool) set(ws ws) {
method all (line 170) | func (p *safePool) all() map[string]ws {
method get (line 180) | func (p *safePool) get(key string) (ws, error) {
method contains (line 190) | func (p *safePool) contains(key string) bool {
method delete (line 197) | func (p *safePool) delete(key string) {
method reset (line 204) | func (p *safePool) reset() {
type safeListeners (line 210) | type safeListeners struct
method set (line 215) | func (l *safeListeners) set(event string, callback eventCallback) {
method get (line 221) | func (l *safeListeners) get(event string) []eventCallback {
function New (line 238) | func New(callback func(kws *Websocket), config ...websocket.Config) func...
function EmitToList (line 359) | func EmitToList(uuids []string, message []byte, mType ...int) {
function EmitTo (line 382) | func EmitTo(uuid string, message []byte, mType ...int) error {
function Broadcast (line 411) | func Broadcast(message []byte, mType ...int) {
function Fire (line 423) | func Fire(event string, data []byte) {
function fireGlobalEvent (line 612) | func fireGlobalEvent(event string, data []byte, error error) {
type eventCallback (line 635) | type eventCallback
function On (line 638) | func On(event string, callback eventCallback) {
FILE: v3/socketio/socketio_test.go
constant numTestConn (line 20) | numTestConn = 10
constant numParallelTestConn (line 21) | numParallelTestConn = 5_000
type HandlerMock (line 23) | type HandlerMock struct
method OnCustomEvent (line 77) | func (h *HandlerMock) OnCustomEvent(payload *EventPayload) {
type WebsocketMock (line 28) | type WebsocketMock struct
method SetUUID (line 43) | func (s *WebsocketMock) SetUUID(uuid string) error {
method GetIntAttribute (line 55) | func (s *WebsocketMock) GetIntAttribute(key string) int {
method GetStringAttribute (line 67) | func (s *WebsocketMock) GetStringAttribute(key string) string {
method Emit (line 82) | func (s *WebsocketMock) Emit(message []byte, _ ...int) {
method IsAlive (line 87) | func (s *WebsocketMock) IsAlive() bool {
method GetUUID (line 92) | func (s *WebsocketMock) GetUUID() string {
method SetAttribute (line 400) | func (s *WebsocketMock) SetAttribute(_ string, _ interface{}) {
method GetAttribute (line 404) | func (s *WebsocketMock) GetAttribute(_ string) interface{} {
method EmitToList (line 408) | func (s *WebsocketMock) EmitToList(_ []string, _ []byte, _ ...int) {
method EmitTo (line 412) | func (s *WebsocketMock) EmitTo(_ string, _ []byte, _ ...int) error {
method Broadcast (line 416) | func (s *WebsocketMock) Broadcast(_ []byte, _ bool, _ ...int) {
method Fire (line 420) | func (s *WebsocketMock) Fire(_ string, _ []byte) {
method Close (line 424) | func (s *WebsocketMock) Close() {
method pong (line 428) | func (s *WebsocketMock) pong(_ context.Context) {
method write (line 432) | func (s *WebsocketMock) write(_ int, _ []byte) {
method run (line 436) | func (s *WebsocketMock) run() {
method read (line 440) | func (s *WebsocketMock) read(_ context.Context) {
method disconnected (line 444) | func (s *WebsocketMock) disconnected(_ error) {
method createUUID (line 448) | func (s *WebsocketMock) createUUID() string {
method randomUUID (line 452) | func (s *WebsocketMock) randomUUID() string {
method fireEvent (line 456) | func (s *WebsocketMock) fireEvent(_ string, _ []byte, _ error) {
function TestParallelConnections (line 96) | func TestParallelConnections(t *testing.T) {
function TestGlobalFire (line 170) | func TestGlobalFire(t *testing.T) {
function TestGlobalBroadcast (line 198) | func TestGlobalBroadcast(t *testing.T) {
function TestGlobalEmitTo (line 222) | func TestGlobalEmitTo(t *testing.T) {
function TestGlobalEmitToList (line 259) | func TestGlobalEmitToList(t *testing.T) {
function TestWebsocket_GetIntAttribute (line 285) | func TestWebsocket_GetIntAttribute(t *testing.T) {
function TestWebsocket_GetStringAttribute (line 303) | func TestWebsocket_GetStringAttribute(t *testing.T) {
function TestWebsocket_SetUUIDUpdatesPool (line 319) | func TestWebsocket_SetUUIDUpdatesPool(t *testing.T) {
function assertPanic (line 352) | func assertPanic(t *testing.T, f func()) {
function createWS (line 361) | func createWS() *Websocket {
function upgradeMiddleware (line 386) | func upgradeMiddleware(c fiber.Ctx) error {
FILE: v3/swaggerui/swagger.go
type Config (line 19) | type Config struct
function New (line 81) | func New(config ...Config) fiber.Handler {
FILE: v3/swaggerui/swagger_test.go
function performRequest (line 20) | func performRequest(method, target string, app *fiber.App) *http.Response {
function TestNew (line 26) | func TestNew(t *testing.T) {
function TestNewWithFileContent (line 270) | func TestNewWithFileContent(t *testing.T) {
FILE: v3/swaggo/config.go
type Config (line 8) | type Config struct
type FilterConfig (line 188) | type FilterConfig struct
method Value (line 193) | func (fc FilterConfig) Value() interface{} {
type SyntaxHighlightConfig (line 200) | type SyntaxHighlightConfig struct
method Value (line 210) | func (shc SyntaxHighlightConfig) Value() interface{} {
type OAuthConfig (line 217) | type OAuthConfig struct
function configDefault (line 281) | func configDefault(config ...Config) Config {
FILE: v3/swaggo/index.go
constant indexTmpl (line 3) | indexTmpl string = `
FILE: v3/swaggo/swagger.go
constant defaultDocURL (line 20) | defaultDocURL = "doc.json"
constant defaultIndex (line 21) | defaultIndex = "index.html"
function New (line 27) | func New(config ...Config) fiber.Handler {
function getForwardedPrefix (line 116) | func getForwardedPrefix(c fiber.Ctx) string {
FILE: v3/swaggo/swagger_test.go
type mockedSwag (line 13) | type mockedSwag struct
method ReadDoc (line 15) | func (s *mockedSwag) ReadDoc() string {
function Test_Swagger (line 43) | func Test_Swagger(t *testing.T) {
function Test_Swagger_Proxy_Redirect (line 128) | func Test_Swagger_Proxy_Redirect(t *testing.T) {
FILE: v3/testcontainers/config.go
type Config (line 10) | type Config struct
function NewModuleConfig (line 35) | func NewModuleConfig[T tc.Container](
function NewContainerConfig (line 59) | func NewContainerConfig(serviceKey string, img string, opts ...tc.Contai...
FILE: v3/testcontainers/examples_test.go
function ExampleAddService_fromContainer (line 15) | func ExampleAddService_fromContainer() {
function ExampleAddService_fromModule (line 51) | func ExampleAddService_fromModule() {
FILE: v3/testcontainers/testcontainers.go
constant serviceSuffix (line 15) | serviceSuffix = " (using testcontainers-go)"
constant fiberContainerLabel (line 18) | fiberContainerLabel = "org.testcontainers.golang.framework"
constant fiberContainerLabelValue (line 21) | fiberContainerLabelValue = "Go Fiber"
function buildKey (line 43) | func buildKey(key string) string {
type ContainerService (line 57) | type ContainerService struct
method Key (line 86) | func (c *ContainerService[T]) Key() string {
method Container (line 93) | func (c *ContainerService[T]) Container() T {
method Start (line 104) | func (c *ContainerService[T]) Start(ctx context.Context) error {
method String (line 127) | func (c *ContainerService[T]) String() string {
method State (line 133) | func (c *ContainerService[T]) State(ctx context.Context) (string, error) {
method Terminate (line 151) | func (c *ContainerService[T]) Terminate(ctx context.Context) error {
function AddService (line 181) | func AddService[T tc.Container](cfg *fiber.Config, containerConfig Confi...
FILE: v3/testcontainers/testcontainers_test.go
constant nginxAlpineImg (line 17) | nginxAlpineImg = "nginx:alpine"
constant redisAlpineImg (line 18) | redisAlpineImg = "redis:alpine"
constant postgresAlpineImg (line 19) | postgresAlpineImg = "postgres:alpine"
function TestAddService_fromContainerConfig (line 22) | func TestAddService_fromContainerConfig(t *testing.T) {
function TestAddService_fromModuleConfig (line 63) | func TestAddService_fromModuleConfig(t *testing.T) {
function TestContainerService (line 114) | func TestContainerService(t *testing.T) {
FILE: v3/testcontainers/testcontainers_unit_test.go
function Test_buildKey (line 12) | func Test_buildKey(t *testing.T) {
function Test_ContainersService_Start (line 26) | func Test_ContainersService_Start(t *testing.T) {
FILE: v3/websocket/websocket.go
type Config (line 23) | type Config struct
function defaultRecover (line 73) | func defaultRecover(c *Conn) {
function New (line 84) | func New(handler func(*Conn), config ...Config) fiber.Handler {
type Conn (line 196) | type Conn struct
method Locals (line 232) | func (conn *Conn) Locals(key string, value ...interface{}) interface{} {
method Params (line 243) | func (conn *Conn) Params(key string, defaultValue ...string) string {
method Query (line 254) | func (conn *Conn) Query(key string, defaultValue ...string) string {
method Cookies (line 265) | func (conn *Conn) Cookies(key string, defaultValue ...string) string {
method Headers (line 277) | func (conn *Conn) Headers(key string, defaultValue ...string) string {
method IP (line 290) | func (conn *Conn) IP() string {
function acquireConn (line 214) | func acquireConn() *Conn {
function releaseConn (line 225) | func releaseConn(conn *Conn) {
constant CloseNormalClosure (line 298) | CloseNormalClosure = 1000
constant CloseGoingAway (line 299) | CloseGoingAway = 1001
constant CloseProtocolError (line 300) | CloseProtocolError = 1002
constant CloseUnsupportedData (line 301) | CloseUnsupportedData = 1003
constant CloseNoStatusReceived (line 302) | CloseNoStatusReceived = 1005
constant CloseAbnormalClosure (line 303) | CloseAbnormalClosure = 1006
constant CloseInvalidFramePayloadData (line 304) | CloseInvalidFramePayloadData = 1007
constant ClosePolicyViolation (line 305) | ClosePolicyViolation = 1008
constant CloseMessageTooBig (line 306) | CloseMessageTooBig = 1009
constant CloseMandatoryExtension (line 307) | CloseMandatoryExtension = 1010
constant CloseInternalServerErr (line 308) | CloseInternalServerErr = 1011
constant CloseServiceRestart (line 309) | CloseServiceRestart = 1012
constant CloseTryAgainLater (line 310) | CloseTryAgainLater = 1013
constant CloseTLSHandshake (line 311) | CloseTLSHandshake = 1015
constant TextMessage (line 318) | TextMessage = 1
constant BinaryMessage (line 321) | BinaryMessage = 2
constant CloseMessage (line 326) | CloseMessage = 8
constant PingMessage (line 330) | PingMessage = 9
constant PongMessage (line 334) | PongMessage = 10
function FormatCloseMessage (line 351) | func FormatCloseMessage(closeCode int, text string) []byte {
function IsCloseError (line 357) | func IsCloseError(err error, codes ...int) bool {
function IsUnexpectedCloseError (line 363) | func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
function IsWebSocketUpgrade (line 369) | func IsWebSocketUpgrade(c fiber.Ctx) bool {
function JoinMessages (line 376) | func JoinMessages(c *websocket.Conn, term string) io.Reader {
FILE: v3/websocket/websocket_test.go
function TestWebSocketMiddlewareDefaultConfig (line 15) | func TestWebSocketMiddlewareDefaultConfig(t *testing.T) {
function TestWebSocketMiddlewareConfigOrigin (line 31) | func TestWebSocketMiddlewareConfigOrigin(t *testing.T) {
function TestWebSocketMiddlewareBufferSize (line 164) | func TestWebSocketMiddlewareBufferSize(t *testing.T) {
function TestWebSocketConnParams (line 183) | func TestWebSocketConnParams(t *testing.T) {
function TestWebSocketConnQuery (line 211) | func TestWebSocketConnQuery(t *testing.T) {
function TestWebSocketConnHeaders (line 239) | func TestWebSocketConnHeaders(t *testing.T) {
function TestWebSocketConnCookies (line 273) | func TestWebSocketConnCookies(t *testing.T) {
function TestWebSocketConnLocals (line 305) | func TestWebSocketConnLocals(t *testing.T) {
function TestWebSocketConnIP (line 331) | func TestWebSocketConnIP(t *testing.T) {
function TestWebSocketConnIPSafeCopy (line 358) | func TestWebSocketConnIPSafeCopy(t *testing.T) {
function TestWebSocketCompressionAfterHandlerReturns (line 386) | func TestWebSocketCompressionAfterHandlerReturns(t *testing.T) {
function setupTestApp (line 429) | func setupTestApp(cfg Config, h func(c *Conn)) *fiber.App {
function TestWebSocketIsCloseError (line 479) | func TestWebSocketIsCloseError(t *testing.T) {
function TestWebSocketIsUnexpectedCloseError (line 486) | func TestWebSocketIsUnexpectedCloseError(t *testing.T) {
function TestWebSocketFormatCloseMessage (line 493) | func TestWebSocketFormatCloseMessage(t *testing.T) {
function TestWebsocketRecoverDefaultHandlerShouldNotPanic (line 499) | func TestWebsocketRecoverDefaultHandlerShouldNotPanic(t *testing.T) {
function TestWebsocketRecoverCustomHandlerShouldNotPanic (line 517) | func TestWebsocketRecoverCustomHandlerShouldNotPanic(t *testing.T) {
FILE: v3/zap/config.go
type Config (line 10) | type Config struct
function configDefault (line 87) | func configDefault(config ...Config) Config {
FILE: v3/zap/logger.go
type LoggerConfig (line 16) | type LoggerConfig struct
method WithContext (line 40) | func (l *LoggerConfig) WithContext(ctx context.Context) fiberlog.Commo...
method SetOutput (line 130) | func (l *LoggerConfig) SetOutput(w io.Writer) {
method SetLevel (line 147) | func (l *LoggerConfig) SetLevel(lv fiberlog.Level) {
method Logf (line 180) | func (l *LoggerConfig) Logf(level fiberlog.Level, format string, kvs ....
method Trace (line 198) | func (l *LoggerConfig) Trace(v ...interface{}) {
method Debug (line 202) | func (l *LoggerConfig) Debug(v ...interface{}) {
method Info (line 206) | func (l *LoggerConfig) Info(v ...interface{}) {
method Warn (line 210) | func (l *LoggerConfig) Warn(v ...interface{}) {
method Error (line 214) | func (l *LoggerConfig) Error(v ...interface{}) {
method Fatal (line 218) | func (l *LoggerConfig) Fatal(v ...interface{}) {
method Panic (line 222) | func (l *LoggerConfig) Panic(v ...interface{}) {
method Tracef (line 226) | func (l *LoggerConfig) Tracef(format string, v ...interface{}) {
method Debugf (line 230) | func (l *LoggerConfig) Debugf(format string, v ...interface{}) {
method Infof (line 234) | func (l *LoggerConfig) Infof(format string, v ...interface{}) {
method Warnf (line 238) | func (l *LoggerConfig) Warnf(format string, v ...interface{}) {
method Errorf (line 242) | func (l *LoggerConfig) Errorf(format string, v ...interface{}) {
method Fatalf (line 246) | func (l *LoggerConfig) Fatalf(format string, v ...interface{}) {
method Panicf (line 250) | func (l *LoggerConfig) Panicf(format string, v ...interface{}) {
method Tracew (line 254) | func (l *LoggerConfig) Tracew(msg string, keysAndValues ...interface{}) {
method Debugw (line 258) | func (l *LoggerConfig) Debugw(msg string, keysAndValues ...interface{}) {
method Infow (line 262) | func (l *LoggerConfig) Infow(msg string, keysAndValues ...interface{}) {
method Warnw (line 266) | func (l *LoggerConfig) Warnw(msg string, keysAndValues ...interface{}) {
method Errorw (line 270) | func (l *LoggerConfig) Errorw(msg string, keysAndValues ...interface{}) {
method Fatalw (line 274) | func (l *LoggerConfig) Fatalw(msg string, keysAndValues ...interface{}) {
method Panicw (line 278) | func (l *LoggerConfig) Panicw(msg string, keysAndValues ...interface{}) {
method Log (line 282) | func (l *LoggerConfig) Log(level fiberlog.Level, kvs ...interface{}) {
method Logw (line 302) | func (l *LoggerConfig) Logw(level fiberlog.Level, msg string, keyvals ...
method Sync (line 329) | func (l *LoggerConfig) Sync() error {
method Logger (line 334) | func (l *LoggerConfig) Logger() *zap.Logger {
type CoreConfig (line 56) | type CoreConfig struct
function loggerConfigDefault (line 77) | func loggerConfigDefault(config ...LoggerConfig) LoggerConfig {
function NewLogger (line 109) | func NewLogger(config ...LoggerConfig) *LoggerConfig {
FILE: v3/zap/logger_test.go
function testEncoderConfig (line 20) | func testEncoderConfig() zapcore.EncoderConfig {
function humanEncoderConfig (line 37) | func humanEncoderConfig() zapcore.EncoderConfig {
function getWriteSyncer (line 45) | func getWriteSyncer(file string) zapcore.WriteSyncer {
function TestCoreOption (line 57) | func TestCoreOption(t *testing.T) {
function TestCoreConfigs (line 126) | func TestCoreConfigs(t *testing.T) {
function TestZapOptions (line 149) | func TestZapOptions(t *testing.T) {
function TestWithContextCaller (line 171) | func TestWithContextCaller(t *testing.T) {
function TestWithExtraKeys (line 192) | func TestWithExtraKeys(t *testing.T) {
function BenchmarkNormal (line 212) | func BenchmarkNormal(b *testing.B) {
function BenchmarkWithExtraKeys (line 222) | func BenchmarkWithExtraKeys(b *testing.B) {
function TestCustomField (line 234) | func TestCustomField(t *testing.T) {
FILE: v3/zap/zap.go
function New (line 15) | func New(config ...Config) fiber.Handler {
function contains (line 198) | func contains(needle string, slice []string) bool {
function sanitizeHeaderValues (line 216) | func sanitizeHeaderValues(header string, values []string) []string {
FILE: v3/zap/zap_test.go
function setupLogsCapture (line 23) | func setupLogsCapture() (*zap.Logger, *observer.ObservedLogs) {
function Test_GetResBody (line 28) | func Test_GetResBody(t *testing.T) {
function Test_SkipBody (line 52) | func Test_SkipBody(t *testing.T) {
function Test_SkipResBody (line 74) | func Test_SkipResBody(t *testing.T) {
function Test_Logger (line 96) | func Test_Logger(t *testing.T) {
function Test_Logger_Next (line 116) | func Test_Logger_Next(t *testing.T) {
function Test_Logger_All (line 130) | func Test_Logger_All(t *testing.T) {
function Test_Query_Params (line 162) | func Test_Query_Params(t *testing.T) {
function Test_Response_Body (line 180) | func Test_Response_Body(t *testing.T) {
function Test_Logger_AppendUint (line 212) | func Test_Logger_AppendUint(t *testing.T) {
function Test_Logger_Data_Race (line 233) | func Test_Logger_Data_Race(t *testing.T) {
function Benchmark_Logger (line 266) | func Benchmark_Logger(b *testing.B) {
function Test_Request_Id (line 291) | func Test_Request_Id(t *testing.T) {
function Test_Skip_URIs (line 312) | func Test_Skip_URIs(t *testing.T) {
function Test_Req_Headers (line 332) | func Test_Req_Headers(t *testing.T) {
function Test_LoggerLevelsAndMessages (line 361) | func Test_LoggerLevelsAndMessages(t *testing.T) {
function Test_LoggerLevelsAndMessagesSingle (line 404) | func Test_LoggerLevelsAndMessagesSingle(t *testing.T) {
function Test_Fields_Func (line 447) | func Test_Fields_Func(t *testing.T) {
FILE: v3/zerolog/config.go
constant FieldReferer (line 12) | FieldReferer = "referer"
constant FieldProtocol (line 13) | FieldProtocol = "protocol"
constant FieldPID (line 14) | FieldPID = "pid"
constant FieldPort (line 15) | FieldPort = "port"
constant FieldIP (line 16) | FieldIP = "ip"
constant FieldIPs (line 17) | FieldIPs = "ips"
constant FieldHost (line 18) | FieldHost = "host"
constant FieldPath (line 19) | FieldPath = "path"
constant FieldURL (line 20) | FieldURL = "url"
constant FieldUserAgent (line 21) | FieldUserAgent = "ua"
constant FieldLatency (line 22) | FieldLatency = "latency"
constant FieldStatus (line 23) | FieldStatus = "status"
constant FieldResBody (line 24) | FieldResBody = "resBody"
constant FieldQueryParams (line 25) | FieldQueryParams = "queryParams"
constant FieldBody (line 26) | FieldBody = "body"
constant FieldBytesReceived (line 27) | FieldBytesReceived = "bytesReceived"
constant FieldBytesSent (line 28) | FieldBytesSent = "bytesSent"
constant FieldRoute (line 29) | FieldRoute = "route"
constant FieldMethod (line 30) | FieldMethod = "method"
constant FieldRequestID (line 31) | FieldRequestID = "requestId"
constant FieldError (line 32) | FieldError = "error"
constant FieldReqHeaders (line 33) | FieldReqHeaders = "reqHeaders"
constant FieldResHeaders (line 34) | FieldResHeaders = "resHeaders"
constant fieldResBody_ (line 36) | fieldResBody_ = "res_body"
constant fieldQueryParams_ (line 37) | fieldQueryParams_ = "query_params"
constant fieldBytesReceived_ (line 38) | fieldBytesReceived_ = "bytes_received"
constant fieldBytesSent_ (line 39) | fieldBytesSent_ = "bytes_sent"
constant fieldRequestID_ (line 40) | fieldRequestID_ = "request_id"
constant fieldReqHeaders_ (line 41) | fieldReqHeaders_ = "req_headers"
constant fieldResHeaders_ (line 42) | fieldResHeaders_ = "res_headers"
type Config (line 46) | type Config struct
method loggerCtx (line 123) | func (c *Config) loggerCtx(fc fiber.Ctx) zerolog.Context {
method logger (line 131) | func (c *Config) logger(fc fiber.Ctx, latency time.Duration, err error...
function configDefault (line 304) | func configDefault(config ...Config) Config {
FILE: v3/zerolog/zerolog.go
function New (line 11) | func New(config ...Config) fiber.Handler {
FILE: v3/zerolog/zerolog_test.go
function Test_GetResBody (line 22) | func Test_GetResBody(t *testing.T) {
function Test_SkipBody (line 52) | func Test_SkipBody(t *testing.T) {
function Test_SkipResBody (line 79) | func Test_SkipResBody(t *testing.T) {
function Test_Logger (line 106) | func Test_Logger(t *testing.T) {
function Test_Latency (line 132) | func Test_Latency(t *testing.T) {
function Test_Logger_Next (line 161) | func Test_Logger_Next(t *testing.T) {
function Test_Logger_All (line 176) | func Test_Logger_All(t *testing.T) {
function Test_Response_Body (line 240) | func Test_Response_Body(t *testing.T) {
function Test_Request_Id (line 267) | func Test_Request_Id(t *testing.T) {
function Test_Skip_URIs (line 296) | func Test_Skip_URIs(t *testing.T) {
function Test_Req_Headers (line 320) | func Test_Req_Headers(t *testing.T) {
function Test_Req_Headers_WrapHeaders (line 358) | func Test_Req_Headers_WrapHeaders(t *testing.T) {
function Test_Res_Headers (line 399) | func Test_Res_Headers(t *testing.T) {
function Test_Res_Headers_WrapHeaders (line 437) | func Test_Res_Headers_WrapHeaders(t *testing.T) {
function Test_FieldsSnakeCase (line 478) | func Test_FieldsSnakeCase(t *testing.T) {
function Test_LoggerLevelsAndMessages (line 540) | func Test_LoggerLevelsAndMessages(t *testing.T) {
function Test_Logger_FromContext (line 614) | func Test_Logger_FromContext(t *testing.T) {
function Test_Logger_WhitelistHeaders (line 638) | func Test_Logger_WhitelistHeaders(t *testing.T) {
function Test_WhitelistHeaders_Resp_Headers (line 703) | func Test_WhitelistHeaders_Resp_Headers(t *testing.T) {
function Test_Logger_BlacklistHeaders (line 741) | func Test_Logger_BlacklistHeaders(t *testing.T) {
function Test_BlacklistHeaders_Resp_Headers (line 782) | func Test_BlacklistHeaders_Resp_Headers(t *testing.T) {
Condensed preview — 200 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (994K chars).
[
{
"path": ".github/CODEOWNERS",
"chars": 22,
"preview": "* @gofiber/maintainers"
},
{
"path": ".github/ISSUE_TEMPLATE/bug-report.yaml",
"chars": 2985,
"preview": "name: \"\\U0001F41B Bug Report\"\ntitle: \"\\U0001F41B [Bug]: \"\ndescription: Create a bug report to help us fix it.\nlabels: [\""
},
{
"path": ".github/ISSUE_TEMPLATE/feature-request.yaml",
"chars": 2149,
"preview": "name: \"\\U0001F680 Feature Request\"\ntitle: \"\\U0001F680 [Feature]: \"\ndescription: Suggest an idea to improve this project."
},
{
"path": ".github/ISSUE_TEMPLATE/question.yaml",
"chars": 1996,
"preview": "name: \"🤔 Question\"\ntitle: \"\\U0001F917 [Question]: \"\ndescription: Ask a question so we can help you easily.\nlabels: [\"🤔 Q"
},
{
"path": ".github/dependabot.yml",
"chars": 10136,
"preview": "# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n# https://docs."
},
{
"path": ".github/release-plan.yml",
"chars": 947,
"preview": "# Release plan for gofiber/contrib.\n#\n# Only modules with ordering constraints need explicit waves.\n# The final wave wit"
},
{
"path": ".github/release.yml",
"chars": 564,
"preview": "# .github/release.yml\n\nchangelog:\n categories:\n - title: '❗ Breaking Changes'\n labels:\n - '❗ BreakingCha"
},
{
"path": ".github/scripts/parallel-go-test.sh",
"chars": 8897,
"preview": "#!/usr/bin/env bash\n# parallel-go-test.sh (refreshed)\n# Recursively find go.mod files under the current directory and ru"
},
{
"path": ".github/workflows/after-release.yml",
"chars": 1150,
"preview": "name: After Release\n\non:\n release:\n types: [published]\n repository_dispatch:\n types: [after-release]\n workflow_"
},
{
"path": ".github/workflows/auto-labeler.yml",
"chars": 411,
"preview": "name: auto-labeler\n\non:\n issues:\n types: [opened, edited, milestoned]\n pull_request_target:\n types: [opened, edi"
},
{
"path": ".github/workflows/cleanup-release-draft.yml",
"chars": 428,
"preview": "name: Cleanup Release Draft\n\non:\n workflow_dispatch:\n inputs:\n tag:\n description: 'Tag or name of the dr"
},
{
"path": ".github/workflows/dependabot-on-demand.yml",
"chars": 255,
"preview": "name: Dependabot On-Demand\n\non:\n repository_dispatch:\n types: [trigger-dependabot]\n workflow_dispatch:\n\njobs:\n tri"
},
{
"path": ".github/workflows/dependabot_automerge.yml",
"chars": 1253,
"preview": "name: Dependabot auto-merge\non:\n workflow_dispatch:\n pull_request_target:\npermissions:\n contents: write\n pull-reques"
},
{
"path": ".github/workflows/lint.yml",
"chars": 607,
"preview": "name: Linter\n\non:\n push:\n branches:\n - master\n - main\n paths-ignore:\n - \"**/*.md\"\n - LICENSE\n"
},
{
"path": ".github/workflows/release-drafter.yml",
"chars": 3693,
"preview": "name: Release Drafter (v3 packages)\n\non:\n push:\n branches:\n - master\n - main\n workflo"
},
{
"path": ".github/workflows/sync-docs.yml",
"chars": 1026,
"preview": "name: Sync docs\n\non:\n push:\n branches: [main, master]\n paths: ['**/*.md']\n release:\n types: [published]\n rep"
},
{
"path": ".github/workflows/test-casbin.yml",
"chars": 677,
"preview": "name: \"Test casbin\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/casbin/**/*.go'\n "
},
{
"path": ".github/workflows/test-circuitbreaker.yml",
"chars": 725,
"preview": "name: \"Test CircuitBreaker\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/circuitbreaker"
},
{
"path": ".github/workflows/test-coraza.yml",
"chars": 677,
"preview": "name: \"Test Coraza\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/coraza/**/*.go'\n "
},
{
"path": ".github/workflows/test-fgprof.yml",
"chars": 677,
"preview": "name: \"Test Fgprof\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/fgprof/**/*.go'\n "
},
{
"path": ".github/workflows/test-hcaptcha.yml",
"chars": 689,
"preview": "name: \"Test hcaptcha\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/hcaptcha/**/*.go'\n "
},
{
"path": ".github/workflows/test-i18n.yml",
"chars": 684,
"preview": "name: \"Test i18n\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/i18n/**/*.go'\n - 'v"
},
{
"path": ".github/workflows/test-jwt.yml",
"chars": 659,
"preview": "name: \"Test jwt\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/jwt/**/*.go'\n - 'v3/"
},
{
"path": ".github/workflows/test-loadshed.yml",
"chars": 708,
"preview": "name: \"Test Loadshed\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/loadshed/**/*.go'\n "
},
{
"path": ".github/workflows/test-monitor.yml",
"chars": 683,
"preview": "name: \"Test Monitor\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/monitor/**/*.go'\n "
},
{
"path": ".github/workflows/test-newrelic.yml",
"chars": 689,
"preview": "name: \"Test newrelic\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/newrelic/**/*.go'\n "
},
{
"path": ".github/workflows/test-opa.yml",
"chars": 659,
"preview": "name: \"Test opa\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/opa/**/*.go'\n - 'v3/"
},
{
"path": ".github/workflows/test-otel.yml",
"chars": 684,
"preview": "name: \"Test otel\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/otel/**/*.go'\n - 'v"
},
{
"path": ".github/workflows/test-paseto.yml",
"chars": 677,
"preview": "name: \"Test paseto\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/paseto/**/*.go'\n "
},
{
"path": ".github/workflows/test-sentry.yml",
"chars": 677,
"preview": "name: \"Test sentry\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/sentry/**/*.go'\n "
},
{
"path": ".github/workflows/test-socketio.yml",
"chars": 690,
"preview": "name: \"Test Socket.io\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/socketio/**/*.go'\n "
},
{
"path": ".github/workflows/test-swaggerui.yml",
"chars": 755,
"preview": "name: \"Test swaggerui\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/swaggerui/**/*.go'\n"
},
{
"path": ".github/workflows/test-swaggo.yml",
"chars": 731,
"preview": "name: \"Test swaggo\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/swaggo/**/*.go'\n "
},
{
"path": ".github/workflows/test-testcontainers.yml",
"chars": 736,
"preview": "name: \"Test Testcontainers Services\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/testc"
},
{
"path": ".github/workflows/test-websocket.yml",
"chars": 714,
"preview": "name: \"Test websocket\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/websocket/**/*.go'\n"
},
{
"path": ".github/workflows/test-zap.yml",
"chars": 659,
"preview": "name: \"Test zap\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/zap/**/*.go'\n - 'v3/"
},
{
"path": ".github/workflows/test-zerolog.yml",
"chars": 683,
"preview": "name: \"Test zerolog\"\n\non:\n push:\n branches:\n - master\n - main\n paths:\n - 'v3/zerolog/**/*.go'\n "
},
{
"path": ".github/workflows/weekly-release.yml",
"chars": 1071,
"preview": "name: Weekly release\n\non:\n schedule:\n - cron: '0 8 * * 4'\n workflow_dispatch:\n inputs:\n draft-tags:\n "
},
{
"path": ".gitignore",
"chars": 353,
"preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n*.tmp"
},
{
"path": "LICENSE",
"chars": 1062,
"preview": "MIT License\n\nCopyright (c) 2021 Fiber\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof t"
},
{
"path": "README.md",
"chars": 7488,
"preview": "---\ntitle: 👋 Welcome\nsidebar_position: 1\n---\n\n> 📦 The canonical copy of this README lives in [v3/README.md](./v3/README."
},
{
"path": "go.work",
"chars": 311,
"preview": "go 1.26.1\n\nuse (\n\t./v3/casbin\n\t./v3/circuitbreaker\n\t./v3/coraza\n\t./v3/fgprof\n\t./v3/hcaptcha\n\t./v3/i18n\n\t./v3/jwt\n\t./v3/l"
},
{
"path": "v3/.golangci.yml",
"chars": 61,
"preview": "version: \"2\"\n\nrun:\n go: \"1.25\"\n timeout: 5m\n tests: false\n"
},
{
"path": "v3/README.md",
"chars": 7660,
"preview": "---\ntitle: 👋 Welcome\nsidebar_position: 1\n---\n\n<div align=\"center\">\n <img height=\"125\" alt=\"Fiber\" src=\"https://raw.gith"
},
{
"path": "v3/casbin/README.md",
"chars": 4573,
"preview": "---\nid: casbin\n---\n\n# Casbin\n\n\n[![Discord"
},
{
"path": "v3/casbin/casbin.go",
"chars": 3252,
"preview": "package casbin\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gofiber/fiber/v3\"\n)\n\n// Middleware ...\ntype Middleware struct {\n\tconfig Co"
},
{
"path": "v3/casbin/casbin_test.go",
"chars": 9855,
"preview": "package casbin\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/casbin/casbin/v2\"\n\n\t\"github.com/casbi"
},
{
"path": "v3/casbin/config.go",
"chars": 2416,
"preview": "package casbin\n\nimport (\n\t\"github.com/casbin/casbin/v2\"\n\t\"github.com/casbin/casbin/v2/persist\"\n\tfileadapter \"github.com/"
},
{
"path": "v3/casbin/go.mod",
"chars": 1100,
"preview": "module github.com/gofiber/contrib/v3/casbin\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/casbin/casbin/v2 v2.135.0\n\tgithub.com/gofi"
},
{
"path": "v3/casbin/go.sum",
"chars": 5698,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/casbin/options.go",
"chars": 1284,
"preview": "package casbin\n\nimport \"strings\"\n\nconst (\n\tMatchAllRule ValidationRule = iota\n\tAtLeastOneRule\n)\n\nvar OptionsDefault = Op"
},
{
"path": "v3/casbin/utils.go",
"chars": 293,
"preview": "package casbin\n\nfunc containsString(s []string, v string) bool {\n\tfor _, vv := range s {\n\t\tif vv == v {\n\t\t\treturn true\n\t"
},
{
"path": "v3/circuitbreaker/README.md",
"chars": 8217,
"preview": "---\nid: circuitbreaker\n---\n\n# Circuit Breaker\n\n"
},
{
"path": "v3/circuitbreaker/circuitbreaker_test.go",
"chars": 17638,
"preview": "package circuitbreaker\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http/httptest\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"testing\""
},
{
"path": "v3/circuitbreaker/go.mod",
"chars": 1201,
"preview": "module github.com/gofiber/contrib/v3/circuitbreaker\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/gofiber/fiber/v3 v3.1.0\n\tgithub.co"
},
{
"path": "v3/circuitbreaker/go.sum",
"chars": 5538,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/coraza/README.md",
"chars": 4438,
"preview": "---\nid: coraza\n---\n\n# Coraza\n\n\n[\n[\n[\n[ LoadMes"
},
{
"path": "v3/i18n/embed_test.go",
"chars": 2077,
"preview": "package i18n\n\nimport (\n\t\"context\"\n\t\"embed\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/"
},
{
"path": "v3/i18n/example/localize/en.yaml",
"chars": 50,
"preview": "welcome: hello\nwelcomeWithName: hello {{ .name }}\n"
},
{
"path": "v3/i18n/example/localize/zh.yaml",
"chars": 44,
"preview": "welcome: 你好\nwelcomeWithName: 你好 {{ .name }}\n"
},
{
"path": "v3/i18n/example/localizeJSON/en.json",
"chars": 67,
"preview": "{\n \"welcome\": \"hello\",\n \"welcomeWithName\": \"hello {{ .name }}\"\n}\n"
},
{
"path": "v3/i18n/example/localizeJSON/zh.json",
"chars": 61,
"preview": "{\n \"welcome\": \"你好\",\n \"welcomeWithName\": \"你好 {{ .name }}\"\n}\n"
},
{
"path": "v3/i18n/example/main.go",
"chars": 927,
"preview": "package main\n\nimport (\n\t\"log\"\n\n\tcontribi18n \"github.com/gofiber/contrib/v3/i18n\"\n\t\"github.com/gofiber/fiber/v3\"\n\tgoi18n "
},
{
"path": "v3/i18n/go.mod",
"chars": 1233,
"preview": "module github.com/gofiber/contrib/v3/i18n\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/gofiber/fiber/v3 v3.1.0\n\tgithub.com/gofiber/"
},
{
"path": "v3/i18n/go.sum",
"chars": 6198,
"preview": "github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=\ngithub.com/BurntSushi/toml v1.6.0/go.m"
},
{
"path": "v3/i18n/i18n.go",
"chars": 3928,
"preview": "package i18n\n\nimport (\n\t\"fmt\"\n\t\"path\"\n\t\"sync\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/gofiber/fiber/v3/log\"\n\t\"githu"
},
{
"path": "v3/i18n/i18n_test.go",
"chars": 9033,
"preview": "package i18n\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n\n\t\""
},
{
"path": "v3/jwt/README.md",
"chars": 12900,
"preview": "---\nid: jwt\n---\n\n# JWT\n\n\n[\n[\n\n[\n[\n[.\n//\n// Currently"
},
{
"path": "v3/otel/example/Dockerfile",
"chars": 190,
"preview": "FROM golang:alpine AS base\nCOPY . /src/\nWORKDIR /src/instrumentation/github.com/gofiber/fiber/otelefiber/example\n\nFROM b"
},
{
"path": "v3/otel/example/README.md",
"chars": 908,
"preview": "---\nid: otel-example\n---\n\n# Example\n\nAn HTTP server using gofiber fiber and instrumentation. The server has a\n`/users/:i"
},
{
"path": "v3/otel/example/docker-compose.yml",
"chars": 448,
"preview": "version: \"3.7\"\nservices:\n fiber-client:\n image: golang:alpine\n networks:\n - example\n command:\n - \"/b"
},
{
"path": "v3/otel/example/go.mod",
"chars": 1365,
"preview": "module github.com/gofiber/contrib/v3/otel/example\n\ngo 1.25.0\n\nreplace github.com/gofiber/contrib/v3/otel => ../\n\nrequire"
},
{
"path": "v3/otel/example/go.sum",
"chars": 6583,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/otel/example/server.go",
"chars": 2171,
"preview": "package main\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"log\"\n\n\t\"go.opentelemetry.io/otel/sdk/resource\"\n\n\t\"github.com/gofiber/fiber"
},
{
"path": "v3/otel/fiber.go",
"chars": 9475,
"preview": "package otel\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gofiber/contrib/v3/otel"
},
{
"path": "v3/otel/fiber_context_test.go",
"chars": 730,
"preview": "package otel\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/stretchr"
},
{
"path": "v3/otel/go.mod",
"chars": 1492,
"preview": "module github.com/gofiber/contrib/v3/otel\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/gofiber/fiber/v3 v3.1.0\n\tgithub.com/gofiber/"
},
{
"path": "v3/otel/go.sum",
"chars": 7375,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/otel/internal/http.go",
"chars": 929,
"preview": "package internal\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\n\t\"go.opentelemetry.io/otel/codes\"\n\t\"go.opentelemetry.io/otel/trace\"\n)\n\n//"
},
{
"path": "v3/otel/internal/http_test.go",
"chars": 1671,
"preview": "package internal\n\nimport (\n\t\"github.com/stretchr/testify/assert\"\n\t\"go.opentelemetry.io/otel/codes\"\n\toteltrace \"go.opente"
},
{
"path": "v3/otel/otel_test/fiber_test.go",
"chars": 22692,
"preview": "package otel_test\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"tim"
},
{
"path": "v3/otel/semconv.go",
"chars": 3552,
"preview": "package otel\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/gofiber/utils/v2\"\n\t\"go.o"
},
{
"path": "v3/paseto/README.md",
"chars": 15071,
"preview": "---\nid: paseto\n---\n\n# Paseto\n\n\n[\n[\n[!["
},
{
"path": "v3/socketio/go.mod",
"chars": 1321,
"preview": "module github.com/gofiber/contrib/v3/socketio\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/fasthttp/websocket v1.5.12\n\tgithub.com/g"
},
{
"path": "v3/socketio/go.sum",
"chars": 5661,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/socketio/socketio.go",
"chars": 15331,
"preview": "package socketio\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gofiber/contrib/v3/websocket\"\n\t\"github.com"
},
{
"path": "v3/socketio/socketio_test.go",
"chars": 9232,
"preview": "package socketio\n\nimport (\n\t\"context\"\n\t\"net\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fasthttp/websocket\"\n\tfw"
},
{
"path": "v3/swaggerui/README.md",
"chars": 3812,
"preview": "---\nid: swaggerui\n---\n\n# Swagger UI\n\n> ⚠️ This module was renamed from `gofiber/contrib/swagger` to `swaggerui` to clear"
},
{
"path": "v3/swaggerui/go.mod",
"chars": 2367,
"preview": "module github.com/gofiber/contrib/v3/swaggerui\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/go-openapi/runtime v0.29.4\n\tgithub.com/"
},
{
"path": "v3/swaggerui/go.sum",
"chars": 10426,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/swaggerui/swagger.go",
"chars": 5332,
"preview": "package swaggerui\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/go-openapi"
},
{
"path": "v3/swaggerui/swagger.json",
"chars": 272,
"preview": "{\n \"consumes\": [\n \"application/json\"\n ],\n \"produces\": [\n \"application/json\"\n ],\n \"schemes\": [\n \"http\"\n ],"
},
{
"path": "v3/swaggerui/swagger.yaml",
"chars": 195,
"preview": "consumes:\n - application/json\nproduces:\n - application/json\nschemes:\n - http\nswagger: \"2.0\"\ninfo:\n description: \"Doc"
},
{
"path": "v3/swaggerui/swagger_missing.json",
"chars": 265,
"preview": "{\n \"consumes\": [\n \"application/json\"\n ],\n \"produces\": [\n \"application/json\"\n ],\n \"schemes\": [\n \"http\"\n ],"
},
{
"path": "v3/swaggerui/swagger_test.go",
"chars": 12732,
"preview": "package swaggerui\n\nimport (\n\t_ \"embed\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gofiber/fiber/v3\""
},
{
"path": "v3/swaggo/README.md",
"chars": 4845,
"preview": "---\nid: swaggo\n---\n\n# Swaggo\n\n> ⚠️ This module was renamed from `gofiber/swagger` to `swaggo` to clearly distinguish it "
},
{
"path": "v3/swaggo/config.go",
"chars": 12130,
"preview": "package swaggo\n\nimport (\n\t\"html/template\"\n)\n\n// Config stores SwaggerUI configuration variables\ntype Config struct {\n\t//"
},
{
"path": "v3/swaggo/go.mod",
"chars": 1982,
"preview": "module github.com/gofiber/contrib/v3/swaggo\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/gofiber/fiber/v3 v3.1.0\n\tgithub.com/gofibe"
},
{
"path": "v3/swaggo/go.sum",
"chars": 9417,
"preview": "github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=\ngithub.com/KyleBanks/depth v1.2.1/go.m"
},
{
"path": "v3/swaggo/index.go",
"chars": 4600,
"preview": "package swaggo\n\nconst indexTmpl string = `\n<!-- HTML for static distribution bundle build -->\n<!DOCTYPE html>\n<html lang"
},
{
"path": "v3/swaggo/swagger.go",
"chars": 2574,
"preview": "package swaggo\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"html/template\"\n\t\"io\"\n\t\"io/fs\"\n\t\"path\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/gofib"
},
{
"path": "v3/swaggo/swagger_test.go",
"chars": 3775,
"preview": "package swaggo\n\nimport (\n\t\"mime\"\n\t\"net/http\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/swaggo/swag"
},
{
"path": "v3/testcontainers/README.md",
"chars": 11903,
"preview": "---\nid: testcontainers\n---\n\n# Testcontainers\n\n\n\n// Config contains the"
},
{
"path": "v3/testcontainers/examples_test.go",
"chars": 3436,
"preview": "package testcontainers_test\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\n\t\"github.com/gofiber/contrib/v3/tes"
},
{
"path": "v3/testcontainers/go.mod",
"chars": 3510,
"preview": "module github.com/gofiber/contrib/v3/testcontainers\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/gofiber/fiber/v3 v3.1.0\n\tgithub.co"
},
{
"path": "v3/testcontainers/go.sum",
"chars": 17092,
"preview": "dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMI"
},
{
"path": "v3/testcontainers/testcontainers.go",
"chars": 6823,
"preview": "package testcontainers\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\ttc \"github.com/"
},
{
"path": "v3/testcontainers/testcontainers_test.go",
"chars": 7475,
"preview": "package testcontainers_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofiber/contrib/v3/testcontainers\"\n\t\"g"
},
{
"path": "v3/testcontainers/testcontainers_unit_test.go",
"chars": 1090,
"preview": "package testcontainers\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/stretchr/testify/req"
},
{
"path": "v3/websocket/README.md",
"chars": 7118,
"preview": "---\nid: websocket\n---\n\n# Websocket\n\n\n["
},
{
"path": "v3/websocket/go.mod",
"chars": 1285,
"preview": "module github.com/gofiber/contrib/v3/websocket\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/fasthttp/websocket v1.5.12\n\tgithub.com/"
},
{
"path": "v3/websocket/go.sum",
"chars": 5942,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/websocket/websocket.go",
"chars": 11868,
"preview": "// 🚀 Fiber is an Express inspired web framework written in Go with 💖\n// 📌 API Documentation: https://fiber.wiki\n// 📝 Git"
},
{
"path": "v3/websocket/websocket_test.go",
"chars": 15206,
"preview": "package websocket\n\nimport (\n\t\"net\"\n\t\"net/http\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/fasthttp/websocket\"\n\t\"github.com/gofiber"
},
{
"path": "v3/zap/.gitignore",
"chars": 37,
"preview": "all/\ndebug/\ninfo/\nwarn/\nerror/\n*.log\n"
},
{
"path": "v3/zap/README.md",
"chars": 6171,
"preview": "---\nid: zap\n---\n\n# Zap\n\n\n[\n\n// Config defines "
},
{
"path": "v3/zap/go.mod",
"chars": 1233,
"preview": "module github.com/gofiber/contrib/v3/zap\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/gofiber/fiber/v3 v3.1.0\n\tgithub.com/gofiber/u"
},
{
"path": "v3/zap/go.sum",
"chars": 6005,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/zap/logger.go",
"chars": 8739,
"preview": "package zap\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\n\tfiberlog \"github.com/gofiber/fiber/v3/log\"\n\t\"go.uber.org/zap\"\n\t\"go"
},
{
"path": "v3/zap/logger_test.go",
"chars": 6497,
"preview": "package zap\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"git"
},
{
"path": "v3/zap/zap.go",
"chars": 5697,
"preview": "package zap\n\nimport (\n\t\"os\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/gofiber/utils/v2\"\n\tutilsstrings"
},
{
"path": "v3/zap/zap_test.go",
"chars": 12757,
"preview": "package zap\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n\n\t\""
},
{
"path": "v3/zerolog/README.md",
"chars": 6347,
"preview": "---\nid: zerolog\n---\n\n# Zerolog\n\n\n[![Disc"
},
{
"path": "v3/zerolog/config.go",
"chars": 8708,
"preview": "package zerolog\n\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/rs/zerolog\"\n)\n\nconst (\n\tFieldRefere"
},
{
"path": "v3/zerolog/go.mod",
"chars": 1225,
"preview": "module github.com/gofiber/contrib/v3/zerolog\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/gofiber/fiber/v3 v3.1.0\n\tgithub.com/rs/ze"
},
{
"path": "v3/zerolog/go.sum",
"chars": 5701,
"preview": "github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=\ngithub.com/andybalholm/brotli v1.2."
},
{
"path": "v3/zerolog/zerolog.go",
"chars": 1820,
"preview": "package zerolog\n\nimport (\n\t\"time\"\n\n\t\"github.com/gofiber/fiber/v3\"\n\t\"github.com/rs/zerolog\"\n)\n\n// New creates a new middl"
},
{
"path": "v3/zerolog/zerolog_test.go",
"chars": 17455,
"preview": "package zerolog\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t"
}
]
About this extraction
This page contains the full source code of the gofiber/contrib GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 200 files (900.7 KB), approximately 301.0k tokens, and a symbol index with 884 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.