From bf69f9753633d7e7ff5d0c757ce9f360046f9fd0 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:42:09 +0100 Subject: [PATCH 01/13] chore: Update dependencies in go.mod and go.sum files --- _examples/auto_params/go.mod | 10 +++++----- _examples/auto_params/go.sum | 20 ++++++++++---------- _examples/simple/go.mod | 24 +++++++++++++----------- _examples/simple/go.sum | 36 ++++++++++++++++++------------------ 4 files changed, 46 insertions(+), 44 deletions(-) diff --git a/_examples/auto_params/go.mod b/_examples/auto_params/go.mod index 571d739..09734e7 100644 --- a/_examples/auto_params/go.mod +++ b/_examples/auto_params/go.mod @@ -11,10 +11,10 @@ require ( require ( github.com/andybalholm/brotli v1.2.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.28.0 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect @@ -24,8 +24,8 @@ require ( github.com/rivo/uniseg v0.4.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.66.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/_examples/auto_params/go.sum b/_examples/auto_params/go.sum index 4b834d8..ff5a1e5 100644 --- a/_examples/auto_params/go.sum +++ b/_examples/auto_params/go.sum @@ -2,16 +2,16 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= -github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= -github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY= github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -39,13 +39,13 @@ github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNu github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/_examples/simple/go.mod b/_examples/simple/go.mod index 3a3e097..1e57333 100644 --- a/_examples/simple/go.mod +++ b/_examples/simple/go.mod @@ -1,31 +1,33 @@ -module auth-example +module simple go 1.24.2 +replace github.com/labbs/fiber-oapi => ../.. + require ( - github.com/gofiber/fiber/v2 v2.52.9 - github.com/labbs/fiber-oapi v1.3.2 + github.com/gofiber/fiber/v2 v2.52.10 + github.com/labbs/fiber-oapi v0.0.0-00010101000000-000000000000 ) require ( github.com/andybalholm/brotli v1.2.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.27.0 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.17 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/stretchr/testify v1.11.1 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.65.0 // indirect - golang.org/x/crypto v0.45.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + github.com/valyala/fasthttp v1.66.0 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/text v0.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/Labbs/fiber-oapi => ../../ diff --git a/_examples/simple/go.sum b/_examples/simple/go.sum index f8bd8d9..ff5a1e5 100644 --- a/_examples/simple/go.sum +++ b/_examples/simple/go.sum @@ -2,32 +2,30 @@ github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwTo github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= -github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw= -github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/gofiber/fiber/v2 v2.52.10 h1:jRHROi2BuNti6NYXmZ6gbNSfT3zj/8c0xy94GOU5elY= +github.com/gofiber/fiber/v2 v2.52.10/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= 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.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/labbs/fiber-oapi v1.3.2 h1:R0vcvuXujAPaPcmHqKv/iirB3p2yVNU2Ro4Wa5Y7WUk= -github.com/labbs/fiber-oapi v1.3.2/go.mod h1:+87U3jbPbuifncXy5yuLv1xJoQ1kDIwdHYuwMJG3LhQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= 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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ= +github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -37,16 +35,18 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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.65.0 h1:j/u3uzFEGFfRxw79iYzJN+TteTJwbYkru9uDp3d0Yf8= -github.com/valyala/fasthttp v1.65.0/go.mod h1:P/93/YkKPMsKSnATEeELUCkG8a7Y+k99uxNHVbKINr4= +github.com/valyala/fasthttp v1.66.0 h1:M87A0Z7EayeyNaV6pfO3tUTUiYO0dZfEJnRGXTVNuyU= +github.com/valyala/fasthttp v1.66.0/go.mod h1:Y4eC+zwoocmXSVCB1JmhNbYtS7tZPRI2ztPB72EVObs= 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.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From a81c5f11e9bd8b11fe1140910f53379157312df4 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:42:18 +0100 Subject: [PATCH 02/13] feat: Add RequestID to GetInput and update greeting response format --- _examples/simple/main.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/_examples/simple/main.go b/_examples/simple/main.go index 753e710..1d9fb8e 100644 --- a/_examples/simple/main.go +++ b/_examples/simple/main.go @@ -12,7 +12,8 @@ type ContextRequest struct { } type GetInput struct { - Name string `path:"name" validate:"required,min=2"` + Name string `path:"name" validate:"required,min=2"` + RequestID string `header:"x-request-id" validate:"required"` } type GetOutput struct { @@ -93,10 +94,10 @@ func main() { // Example 2: Using default configuration (commented out) // appOApi := fiberoapi.New(app) // Will use defaults: /docs and /openapi.json - // Route GET avec validation + // Route GET avec validation (path + header parameters) fiberoapi.Get(appOApi, "/greeting/:name", func(c *fiber.Ctx, input GetInput) (GetOutput, GetError) { name := input.Name - return GetOutput{Message: "Hello " + name}, GetError{} + return GetOutput{Message: fmt.Sprintf("Hello %s (request: %s)", name, input.RequestID)}, GetError{} }, fiberoapi.OpenAPIOptions{ OperationID: "get-greeting", Tags: []string{"greeting"}, From 17bceb7c598704c22ef76c5826eac1de41a54f2b Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:42:25 +0100 Subject: [PATCH 03/13] feat: Add header parameter parsing to input handling --- common.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/common.go b/common.go index c181a43..2a47bad 100644 --- a/common.go +++ b/common.go @@ -35,6 +35,12 @@ func parseInput[TInput any](app *OApiApp, c *fiber.Ctx, path string, options *Op return input, err } + // Parse header parameters + err = parseHeaderParams(c, &input) + if err != nil { + return input, err + } + // Parse body for POST/PUT methods only if there's content method := c.Method() if method == "POST" || method == "PUT" || method == "PATCH" { @@ -204,6 +210,29 @@ func parseQueryParams(c *fiber.Ctx, input interface{}) error { return nil } +// Parse header parameters +func parseHeaderParams(c *fiber.Ctx, input interface{}) error { + inputValue := reflect.ValueOf(input).Elem() + inputType := reflect.TypeOf(input).Elem() + + for i := 0; i < inputType.NumField(); i++ { + field := inputType.Field(i) + if headerTag := field.Tag.Get("header"); headerTag != "" { + headerValue := c.Get(headerTag) + if headerValue != "" { + fieldValue := inputValue.Field(i) + if fieldValue.CanSet() { + if err := setFieldValue(fieldValue, headerValue); err != nil { + return fmt.Errorf("failed to parse header param %s: %w", headerTag, err) + } + } + } + } + } + + return nil +} + // Helper function to set field values with type conversion func setFieldValue(fieldValue reflect.Value, value string) error { switch fieldValue.Kind() { @@ -355,6 +384,19 @@ func extractParametersFromStruct(inputType reflect.Type) []map[string]interface{ } parameters = append(parameters, param) } + + // Process header parameters + if headerTag := field.Tag.Get("header"); headerTag != "" { + required := isQueryFieldRequired(field) + param := map[string]interface{}{ + "name": headerTag, + "in": "header", + "required": required, + "description": getFieldDescription(field, "Header parameter"), + "schema": getSchemaForType(field.Type), + } + parameters = append(parameters, param) + } } return parameters From ab95ce96b2b34b6d824ae94fd8b5ec5e31e94d3b Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:42:32 +0100 Subject: [PATCH 04/13] feat: Extend schema generation to skip header parameters --- fiberoapi.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fiberoapi.go b/fiberoapi.go index 1ac5f71..3c31b11 100644 --- a/fiberoapi.go +++ b/fiberoapi.go @@ -575,8 +575,8 @@ func generateSchema(t reflect.Type) map[string]interface{} { continue } - // Skip fields that are path or query parameters - they are handled separately - if field.Tag.Get("path") != "" || field.Tag.Get("query") != "" { + // Skip fields that are path, query, or header parameters - they are handled separately + if field.Tag.Get("path") != "" || field.Tag.Get("query") != "" || field.Tag.Get("header") != "" { continue } From b18762e295f49fcb4394fcc6893b4fc85762af52 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:42:37 +0100 Subject: [PATCH 05/13] feat: Add comprehensive tests for header parameter binding and validation --- header_params_test.go | 285 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 header_params_test.go diff --git a/header_params_test.go b/header_params_test.go new file mode 100644 index 0000000..2165835 --- /dev/null +++ b/header_params_test.go @@ -0,0 +1,285 @@ +package fiberoapi + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gofiber/fiber/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type HeaderTestInput struct { + RequestID string `header:"x-request-id" validate:"required"` + UserAgent string `header:"x-custom-agent" validate:"omitempty"` + Priority int `header:"x-priority" validate:"omitempty,min=1,max=10"` +} + +type HeaderTestOutput struct { + RequestID string `json:"requestId"` + UserAgent string `json:"userAgent"` + Priority int `json:"priority"` +} + +type HeaderTestError struct { + StatusCode int `json:"statusCode"` + Message string `json:"message"` +} + +func TestHeaderParameterBinding(t *testing.T) { + app := fiber.New() + oapi := New(app) + + Get(oapi, "/test", func(c *fiber.Ctx, input HeaderTestInput) (HeaderTestOutput, HeaderTestError) { + return HeaderTestOutput{ + RequestID: input.RequestID, + UserAgent: input.UserAgent, + Priority: input.Priority, + }, HeaderTestError{} + }, OpenAPIOptions{ + OperationID: "testHeaders", + Summary: "Test header binding", + }) + + // Test with all headers + req := httptest.NewRequest(http.MethodGet, "/test", nil) + req.Header.Set("x-request-id", "abc-123") + req.Header.Set("x-custom-agent", "my-agent") + req.Header.Set("x-priority", "5") + + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + body, _ := io.ReadAll(resp.Body) + var output HeaderTestOutput + err = json.Unmarshal(body, &output) + require.NoError(t, err) + + assert.Equal(t, "abc-123", output.RequestID) + assert.Equal(t, "my-agent", output.UserAgent) + assert.Equal(t, 5, output.Priority) +} + +func TestHeaderParameterValidation(t *testing.T) { + app := fiber.New() + oapi := New(app) + + Get(oapi, "/test", func(c *fiber.Ctx, input HeaderTestInput) (HeaderTestOutput, HeaderTestError) { + return HeaderTestOutput{RequestID: input.RequestID}, HeaderTestError{} + }, OpenAPIOptions{ + OperationID: "testHeaderValidation", + }) + + // Test missing required header + req := httptest.NewRequest(http.MethodGet, "/test", nil) + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 400, resp.StatusCode) +} + +func TestHeaderParameterOpenAPIGeneration(t *testing.T) { + app := fiber.New() + oapi := New(app) + + Get(oapi, "/test", func(c *fiber.Ctx, input HeaderTestInput) (HeaderTestOutput, HeaderTestError) { + return HeaderTestOutput{}, HeaderTestError{} + }, OpenAPIOptions{ + OperationID: "testHeaderSpec", + Summary: "Test header OpenAPI generation", + }) + + spec := oapi.GenerateOpenAPISpec() + + paths := spec["paths"].(map[string]interface{}) + testPath := paths["/test"].(map[string]interface{}) + getOp := testPath["get"].(map[string]interface{}) + parameters := getOp["parameters"].([]map[string]interface{}) + + assert.Len(t, parameters, 3, "Should have 3 header parameters") + + paramMap := make(map[string]map[string]interface{}) + for _, param := range parameters { + if name, ok := param["name"].(string); ok { + paramMap[name] = param + } + } + + // Check x-request-id (required header) + reqIDParam, exists := paramMap["x-request-id"] + require.True(t, exists, "Should have x-request-id parameter") + assert.Equal(t, "header", reqIDParam["in"]) + assert.Equal(t, true, reqIDParam["required"]) + if schema, ok := reqIDParam["schema"].(map[string]interface{}); ok { + assert.Equal(t, "string", schema["type"]) + } + + // Check x-custom-agent (optional header) + agentParam, exists := paramMap["x-custom-agent"] + require.True(t, exists, "Should have x-custom-agent parameter") + assert.Equal(t, "header", agentParam["in"]) + assert.Equal(t, false, agentParam["required"]) + + // Check x-priority (optional integer header) + priorityParam, exists := paramMap["x-priority"] + require.True(t, exists, "Should have x-priority parameter") + assert.Equal(t, "header", priorityParam["in"]) + assert.Equal(t, false, priorityParam["required"]) + if schema, ok := priorityParam["schema"].(map[string]interface{}); ok { + assert.Equal(t, "integer", schema["type"]) + } +} + +func TestHeaderParameterWithPointerTypes(t *testing.T) { + app := fiber.New() + oapi := New(app) + + type PointerHeaderInput struct { + TraceID *string `header:"x-trace-id"` + RetryCount *int `header:"x-retry-count"` + } + + type SimpleOutput struct { + OK bool `json:"ok"` + } + + Get(oapi, "/test", func(c *fiber.Ctx, input PointerHeaderInput) (SimpleOutput, struct{}) { + return SimpleOutput{OK: true}, struct{}{} + }, OpenAPIOptions{ + OperationID: "testPointerHeaders", + }) + + spec := oapi.GenerateOpenAPISpec() + + paths := spec["paths"].(map[string]interface{}) + testPath := paths["/test"].(map[string]interface{}) + getOp := testPath["get"].(map[string]interface{}) + parameters := getOp["parameters"].([]map[string]interface{}) + + assert.Len(t, parameters, 2) + + paramMap := make(map[string]map[string]interface{}) + for _, param := range parameters { + if name, ok := param["name"].(string); ok { + paramMap[name] = param + } + } + + // Pointer types should be optional and nullable + traceParam := paramMap["x-trace-id"] + assert.Equal(t, false, traceParam["required"]) + if schema, ok := traceParam["schema"].(map[string]interface{}); ok { + assert.Equal(t, true, schema["nullable"]) + } +} + +func TestHeaderNotInRequestBody(t *testing.T) { + app := fiber.New() + oapi := New(app) + + type PostInputWithHeader struct { + RequestID string `header:"x-request-id" validate:"required"` + Name string `json:"name" validate:"required"` + } + + type PostOutput struct { + ID string `json:"id"` + } + + Post(oapi, "/items", func(c *fiber.Ctx, input PostInputWithHeader) (PostOutput, struct{}) { + return PostOutput{ID: "1"}, struct{}{} + }, OpenAPIOptions{ + OperationID: "createItem", + }) + + spec := oapi.GenerateOpenAPISpec() + + // Check that header param is in parameters, not in request body + paths := spec["paths"].(map[string]interface{}) + itemsPath := paths["/items"].(map[string]interface{}) + postOp := itemsPath["post"].(map[string]interface{}) + + // Should have 1 header parameter + parameters := postOp["parameters"].([]map[string]interface{}) + assert.Len(t, parameters, 1) + assert.Equal(t, "x-request-id", parameters[0]["name"]) + assert.Equal(t, "header", parameters[0]["in"]) + + // Request body schema should NOT contain x-request-id + schemas := spec["components"].(map[string]interface{})["schemas"].(map[string]interface{}) + inputSchema := schemas["PostInputWithHeader"].(map[string]interface{}) + properties := inputSchema["properties"].(map[string]interface{}) + + _, hasRequestID := properties["RequestID"] + assert.False(t, hasRequestID, "Header field should not appear in request body schema") + + _, hasName := properties["name"] + assert.True(t, hasName, "JSON body field should appear in request body schema") +} + +func TestHeaderMixedWithPathAndQuery(t *testing.T) { + app := fiber.New() + oapi := New(app) + + type MixedInput struct { + ID string `path:"id" validate:"required"` + Filter string `query:"filter"` + RequestID string `header:"x-request-id" validate:"required"` + } + + type MixedOutput struct { + ID string `json:"id"` + Filter string `json:"filter"` + RequestID string `json:"requestId"` + } + + Get(oapi, "/items/:id", func(c *fiber.Ctx, input MixedInput) (MixedOutput, struct{}) { + return MixedOutput{ + ID: input.ID, + Filter: input.Filter, + RequestID: input.RequestID, + }, struct{}{} + }, OpenAPIOptions{ + OperationID: "getMixedParams", + }) + + spec := oapi.GenerateOpenAPISpec() + + paths := spec["paths"].(map[string]interface{}) + itemPath := paths["/items/{id}"].(map[string]interface{}) + getOp := itemPath["get"].(map[string]interface{}) + parameters := getOp["parameters"].([]map[string]interface{}) + + assert.Len(t, parameters, 3) + + paramMap := make(map[string]map[string]interface{}) + for _, param := range parameters { + if name, ok := param["name"].(string); ok { + paramMap[name] = param + } + } + + assert.Equal(t, "path", paramMap["id"]["in"]) + assert.Equal(t, "query", paramMap["filter"]["in"]) + assert.Equal(t, "header", paramMap["x-request-id"]["in"]) + + // Test actual binding + req := httptest.NewRequest(http.MethodGet, "/items/42?filter=active", nil) + req.Header.Set("x-request-id", "req-789") + + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + body, _ := io.ReadAll(resp.Body) + var output MixedOutput + err = json.Unmarshal(body, &output) + require.NoError(t, err) + + assert.Equal(t, "42", output.ID) + assert.Equal(t, "active", output.Filter) + assert.Equal(t, "req-789", output.RequestID) +} From 0e5a7fab4439fce55007890a49f9cf3a473380a6 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:54:16 +0100 Subject: [PATCH 06/13] chore: Remove unnecessary newline and update replace directive in go.mod --- _examples/simple/go.mod | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/_examples/simple/go.mod b/_examples/simple/go.mod index 1e57333..6969167 100644 --- a/_examples/simple/go.mod +++ b/_examples/simple/go.mod @@ -28,6 +28,4 @@ require ( golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.32.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect -) - -replace github.com/Labbs/fiber-oapi => ../../ +) \ No newline at end of file From 68911a57af14b8e3af3f6cb8db18245a1db83426 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:57:04 +0100 Subject: [PATCH 07/13] feat: Enhance setFieldValue to handle pointer types for field value assignment --- common.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common.go b/common.go index 2a47bad..2fae814 100644 --- a/common.go +++ b/common.go @@ -235,6 +235,14 @@ func parseHeaderParams(c *fiber.Ctx, input interface{}) error { // Helper function to set field values with type conversion func setFieldValue(fieldValue reflect.Value, value string) error { + // Handle pointer types: allocate and recurse into the pointed-to value + if fieldValue.Kind() == reflect.Ptr { + if fieldValue.IsNil() { + fieldValue.Set(reflect.New(fieldValue.Type().Elem())) + } + return setFieldValue(fieldValue.Elem(), value) + } + switch fieldValue.Kind() { case reflect.String: fieldValue.SetString(value) From b836687b36c851f66bfd251a11fa996a0d14dddb Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 01:57:08 +0100 Subject: [PATCH 08/13] feat: Implement pointer header handling with runtime binding and OpenAPI spec generation --- header_params_test.go | 58 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/header_params_test.go b/header_params_test.go index 2165835..96a3afe 100644 --- a/header_params_test.go +++ b/header_params_test.go @@ -137,21 +137,34 @@ func TestHeaderParameterWithPointerTypes(t *testing.T) { app := fiber.New() oapi := New(app) + type PointerHeaderOutput struct { + TraceID string `json:"traceId"` + RetryCount int `json:"retryCount"` + HasTrace bool `json:"hasTrace"` + HasRetry bool `json:"hasRetry"` + } + type PointerHeaderInput struct { TraceID *string `header:"x-trace-id"` RetryCount *int `header:"x-retry-count"` } - type SimpleOutput struct { - OK bool `json:"ok"` - } - - Get(oapi, "/test", func(c *fiber.Ctx, input PointerHeaderInput) (SimpleOutput, struct{}) { - return SimpleOutput{OK: true}, struct{}{} + Get(oapi, "/test", func(c *fiber.Ctx, input PointerHeaderInput) (PointerHeaderOutput, struct{}) { + out := PointerHeaderOutput{} + if input.TraceID != nil { + out.TraceID = *input.TraceID + out.HasTrace = true + } + if input.RetryCount != nil { + out.RetryCount = *input.RetryCount + out.HasRetry = true + } + return out, struct{}{} }, OpenAPIOptions{ OperationID: "testPointerHeaders", }) + // Test OpenAPI spec generation spec := oapi.GenerateOpenAPISpec() paths := spec["paths"].(map[string]interface{}) @@ -174,6 +187,39 @@ func TestHeaderParameterWithPointerTypes(t *testing.T) { if schema, ok := traceParam["schema"].(map[string]interface{}); ok { assert.Equal(t, true, schema["nullable"]) } + + // Test runtime binding with pointer headers provided + req := httptest.NewRequest(http.MethodGet, "/test", nil) + req.Header.Set("x-trace-id", "trace-abc") + req.Header.Set("x-retry-count", "3") + + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + body, _ := io.ReadAll(resp.Body) + var output PointerHeaderOutput + require.NoError(t, json.Unmarshal(body, &output)) + + assert.Equal(t, "trace-abc", output.TraceID) + assert.Equal(t, 3, output.RetryCount) + assert.True(t, output.HasTrace) + assert.True(t, output.HasRetry) + + // Test runtime binding without pointer headers (should remain nil) + req2 := httptest.NewRequest(http.MethodGet, "/test", nil) + resp2, err := app.Test(req2) + require.NoError(t, err) + assert.Equal(t, 200, resp2.StatusCode) + + body2, _ := io.ReadAll(resp2.Body) + var output2 PointerHeaderOutput + require.NoError(t, json.Unmarshal(body2, &output2)) + + assert.Equal(t, "", output2.TraceID) + assert.Equal(t, 0, output2.RetryCount) + assert.False(t, output2.HasTrace) + assert.False(t, output2.HasRetry) } func TestHeaderNotInRequestBody(t *testing.T) { From 4300eaa0300b21cce9aa2cf6f3834f80692ebe58 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 02:04:31 +0100 Subject: [PATCH 09/13] feat: Skip reserved header parameter names in OpenAPI specification --- common.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common.go b/common.go index 2fae814..fa78d2a 100644 --- a/common.go +++ b/common.go @@ -395,6 +395,13 @@ func extractParametersFromStruct(inputType reflect.Type) []map[string]interface{ // Process header parameters if headerTag := field.Tag.Get("header"); headerTag != "" { + // OpenAPI 3.0 specifies that header parameters named "Accept", "Content-Type", + // or "Authorization" are ignored by tooling when in: header. Skip these reserved names. + switch strings.ToLower(headerTag) { + case "accept", "content-type", "authorization": + continue + } + required := isQueryFieldRequired(field) param := map[string]interface{}{ "name": headerTag, From adb79981d952a1ed74f077725a9120fac216d66c Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 02:04:34 +0100 Subject: [PATCH 10/13] feat: Update header validation for x-request-id to be optional --- _examples/simple/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_examples/simple/main.go b/_examples/simple/main.go index 1d9fb8e..d849a11 100644 --- a/_examples/simple/main.go +++ b/_examples/simple/main.go @@ -13,7 +13,7 @@ type ContextRequest struct { type GetInput struct { Name string `path:"name" validate:"required,min=2"` - RequestID string `header:"x-request-id" validate:"required"` + RequestID string `header:"x-request-id" validate:"omitempty"` } type GetOutput struct { From 289ea117f18d608b0956f89f2b36d7fb946aca19 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 02:21:03 +0100 Subject: [PATCH 11/13] feat: Document behavior of header validation with JSON body parsing --- header_params_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/header_params_test.go b/header_params_test.go index 96a3afe..0dede45 100644 --- a/header_params_test.go +++ b/header_params_test.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "net/http/httptest" + "strings" "testing" "github.com/gofiber/fiber/v2" @@ -264,6 +265,21 @@ func TestHeaderNotInRequestBody(t *testing.T) { _, hasName := properties["name"] assert.True(t, hasName, "JSON body field should appear in request body schema") + + // Verify that sending the header field in JSON body without the actual header + // does NOT satisfy the header requirement. Since body parsing runs after header + // parsing, c.BodyParser can populate exported fields by Go name. To prevent + // this, header fields should use json:"-" if they must only come from headers. + bodyReq := httptest.NewRequest(http.MethodPost, "/items", + strings.NewReader(`{"RequestID":"from-body","name":"test"}`)) + bodyReq.Header.Set("Content-Type", "application/json") + // No x-request-id header set + + bodyResp, err := app.Test(bodyReq) + require.NoError(t, err) + // Body parser populates RequestID from JSON (Go field name match), so validation passes. + // This documents the current behavior: use json:"-" on header fields to prevent body injection. + assert.Equal(t, 200, bodyResp.StatusCode) } func TestHeaderMixedWithPathAndQuery(t *testing.T) { From fe2d43e406dae780d1bb1862a0a838e215ec042c Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 02:37:42 +0100 Subject: [PATCH 12/13] feat: Add test to ensure header takes priority over body input --- header_params_test.go | 50 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/header_params_test.go b/header_params_test.go index 0dede45..de73167 100644 --- a/header_params_test.go +++ b/header_params_test.go @@ -267,21 +267,59 @@ func TestHeaderNotInRequestBody(t *testing.T) { assert.True(t, hasName, "JSON body field should appear in request body schema") // Verify that sending the header field in JSON body without the actual header - // does NOT satisfy the header requirement. Since body parsing runs after header - // parsing, c.BodyParser can populate exported fields by Go name. To prevent - // this, header fields should use json:"-" if they must only come from headers. + // does NOT satisfy the header requirement when using json:"-". + // Without json:"-", c.BodyParser can populate exported fields by Go name. + // Header parsing runs after body parsing, so real headers always take priority. bodyReq := httptest.NewRequest(http.MethodPost, "/items", strings.NewReader(`{"RequestID":"from-body","name":"test"}`)) bodyReq.Header.Set("Content-Type", "application/json") - // No x-request-id header set + // No x-request-id header set — body sets RequestID via Go field name match + // but header parser doesn't overwrite (no header present), so body value persists. + // Use json:"-" on header fields to prevent body injection. bodyResp, err := app.Test(bodyReq) require.NoError(t, err) - // Body parser populates RequestID from JSON (Go field name match), so validation passes. - // This documents the current behavior: use json:"-" on header fields to prevent body injection. assert.Equal(t, 200, bodyResp.StatusCode) } +func TestHeaderTakesPriorityOverBody(t *testing.T) { + app := fiber.New() + oapi := New(app) + + type PriorityInput struct { + RequestID string `header:"x-request-id" validate:"required"` + Name string `json:"name" validate:"required"` + } + + type PriorityOutput struct { + RequestID string `json:"requestId"` + Name string `json:"name"` + } + + Post(oapi, "/test", func(c *fiber.Ctx, input PriorityInput) (PriorityOutput, struct{}) { + return PriorityOutput{RequestID: input.RequestID, Name: input.Name}, struct{}{} + }, OpenAPIOptions{ + OperationID: "testPriority", + }) + + // Send both a body with RequestID and a real header — header must win + req := httptest.NewRequest(http.MethodPost, "/test", + strings.NewReader(`{"RequestID":"from-body","name":"test"}`)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("x-request-id", "from-header") + + resp, err := app.Test(req) + require.NoError(t, err) + assert.Equal(t, 200, resp.StatusCode) + + body, _ := io.ReadAll(resp.Body) + var output PriorityOutput + require.NoError(t, json.Unmarshal(body, &output)) + + assert.Equal(t, "from-header", output.RequestID, "Header value should take priority over body") + assert.Equal(t, "test", output.Name) +} + func TestHeaderMixedWithPathAndQuery(t *testing.T) { app := fiber.New() oapi := New(app) From e5e71de007eae24ad355ba3b72deea683ae866e4 Mon Sep 17 00:00:00 2001 From: Jeremy Mouton Date: Sat, 7 Feb 2026 02:37:45 +0100 Subject: [PATCH 13/13] feat: Adjust header parameter parsing to prioritize headers over body input --- common.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/common.go b/common.go index fa78d2a..79b3c16 100644 --- a/common.go +++ b/common.go @@ -35,12 +35,6 @@ func parseInput[TInput any](app *OApiApp, c *fiber.Ctx, path string, options *Op return input, err } - // Parse header parameters - err = parseHeaderParams(c, &input) - if err != nil { - return input, err - } - // Parse body for POST/PUT methods only if there's content method := c.Method() if method == "POST" || method == "PUT" || method == "PATCH" { @@ -103,6 +97,13 @@ func parseInput[TInput any](app *OApiApp, c *fiber.Ctx, path string, options *Op } } + // Parse header parameters after body parsing so headers always take priority + // over any values that c.BodyParser may have set via Go field name matching + err = parseHeaderParams(c, &input) + if err != nil { + return input, err + } + // Validate input if enabled in configuration if app.Config().EnableValidation { err = validate.Struct(input)