Repository: danielgtaylor/huma
Pull request #276 (2024-03-03 05:02:48)

feat: support input param types using `TextUnmarshaler`
This is the same as #270, just working on getting the tests to pass.

Fixes #270.


Patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 24188f8..2ab8025 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -16,6 +16,6 @@ jobs:
       - name: golangci-lint
         uses: golangci/golangci-lint-action@v3
         with:
-          version: v1.55.2
+          version: v1.56.2
       - run: go test -coverprofile=coverage.txt -covermode=atomic ./...
       - uses: codecov/codecov-action@v1
diff --git a/examples/go.mod b/examples/go.mod
index c6f6df9..4dde318 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -9,7 +9,7 @@ require (
 	github.com/danielgtaylor/huma/v2 v2.0.0-00010101000000-000000000000
 	github.com/danielgtaylor/shorthand/v2 v2.2.0
 	github.com/go-chi/chi v4.1.2+incompatible
-	github.com/go-chi/chi/v5 v5.0.11
+	github.com/go-chi/chi/v5 v5.0.12
 	github.com/spf13/cobra v1.8.0
 	google.golang.org/protobuf v1.32.0
 )
@@ -23,7 +23,7 @@ require (
 	github.com/fatih/color v1.15.0 // indirect
 	github.com/fatih/structs v1.1.0 // indirect
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
-	github.com/fxamacker/cbor/v2 v2.5.0 // indirect
+	github.com/fxamacker/cbor/v2 v2.6.0 // indirect
 	github.com/goccy/go-yaml v1.11.2 // indirect
 	github.com/graphql-go/graphql v0.8.1 // indirect
 	github.com/graphql-go/handler v0.2.3 // indirect
@@ -51,7 +51,7 @@ require (
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
 	go.uber.org/zap v1.21.0 // indirect
-	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
diff --git a/examples/go.sum b/examples/go.sum
index 4f55edd..e045c54 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -140,12 +140,14 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
 github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
 github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
 github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
+github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
 github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
 github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
 github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
 github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -649,6 +651,7 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/go.mod b/go.mod
index c2604e3..a3a1adc 100644
--- a/go.mod
+++ b/go.mod
@@ -6,41 +6,41 @@ require (
 	github.com/danielgtaylor/casing v1.0.0
 	github.com/danielgtaylor/shorthand/v2 v2.2.0
 	github.com/evanphx/json-patch/v5 v5.9.0
-	github.com/fxamacker/cbor/v2 v2.5.0
+	github.com/fxamacker/cbor/v2 v2.6.0
 	github.com/gin-gonic/gin v1.9.1
 	github.com/go-chi/chi v4.1.2+incompatible
-	github.com/go-chi/chi/v5 v5.0.11
+	github.com/go-chi/chi/v5 v5.0.12
 	github.com/gofiber/fiber/v2 v2.52.1
+	github.com/google/uuid v1.6.0
 	github.com/gorilla/mux v1.8.1
 	github.com/julienschmidt/httprouter v1.3.0
 	github.com/labstack/echo/v4 v4.11.4
 	github.com/spf13/cobra v1.8.0
-	github.com/stretchr/testify v1.8.4
+	github.com/stretchr/testify v1.9.0
 	github.com/uptrace/bunrouter v1.0.21
 )
 
 require (
 	github.com/andybalholm/brotli v1.1.0 // indirect
-	github.com/bytedance/sonic v1.10.2 // indirect
+	github.com/bytedance/sonic v1.11.2 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
-	github.com/chenzhuoyu/iasm v0.9.0 // indirect
+	github.com/chenzhuoyu/iasm v0.9.1 // indirect
 	github.com/danielgtaylor/mexpr v1.9.0 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 	github.com/gin-contrib/sse v0.1.0 // 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.17.0 // indirect
+	github.com/go-playground/validator/v10 v10.18.0 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/google/go-cmp v0.6.0 // indirect
-	github.com/google/uuid v1.6.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/klauspost/compress v1.17.5 // indirect
-	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
+	github.com/klauspost/compress v1.17.7 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 	github.com/kr/pretty v0.3.1 // indirect
 	github.com/labstack/gommon v0.4.2 // indirect
-	github.com/leodido/go-urn v1.3.0 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
 	github.com/mattn/go-runewidth v0.0.15 // indirect
@@ -49,19 +49,19 @@ require (
 	github.com/pelletier/go-toml/v2 v2.1.1 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/rivo/uniseg v0.4.6 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.12 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
-	github.com/valyala/fasthttp v1.51.0 // indirect
+	github.com/valyala/fasthttp v1.52.0 // indirect
 	github.com/valyala/fasttemplate v1.2.2 // indirect
 	github.com/valyala/tcplisten v1.0.0 // indirect
 	github.com/x448/float16 v0.8.4 // indirect
 	golang.org/x/arch v0.7.0 // indirect
-	golang.org/x/crypto v0.18.0 // indirect
-	golang.org/x/net v0.20.0 // indirect
-	golang.org/x/sys v0.16.0 // indirect
+	golang.org/x/crypto v0.20.0 // indirect
+	golang.org/x/net v0.21.0 // indirect
+	golang.org/x/sys v0.17.0 // indirect
 	golang.org/x/text v0.14.0 // indirect
 	google.golang.org/protobuf v1.32.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 686c1f9..bdabcbf 100644
--- a/go.sum
+++ b/go.sum
@@ -4,12 +4,16 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1
 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
 github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
 github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
+github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A=
+github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
 github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
 github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
 github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
 github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
+github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
 github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/danielgtaylor/casing v1.0.0 h1:uX+PewTv0zbXeTluwRwlyPMRQEduVP9svLHpbDsQYkw=
@@ -25,6 +29,8 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0
 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
 github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
 github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
+github.com/fxamacker/cbor/v2 v2.6.0 h1:sU6J2usfADwWlYDAFhZBQ6TnLFBHxgesMrQfQgk1tWA=
+github.com/fxamacker/cbor/v2 v2.6.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
 github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
 github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -35,6 +41,8 @@ github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyN
 github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
 github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
 github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
+github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
+github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 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=
@@ -42,6 +50,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
 github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 github.com/go-playground/validator/v10 v10.17.0 h1:SmVVlfAOtlZncTxRuinDPomC2DkXJ4E5T9gDA0AIH74=
 github.com/go-playground/validator/v10 v10.17.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
+github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlgHI=
@@ -61,9 +71,13 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
 github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
 github.com/klauspost/compress v1.17.5 h1:d4vBd+7CHydUqpFBgUEKkSdtSugf9YFmSkvUYPquI5E=
 github.com/klauspost/compress v1.17.5/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
+github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
 github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -75,6 +89,8 @@ github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0
 github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
 github.com/leodido/go-urn v1.3.0 h1:jX8FDLfW4ThVXctBNZ+3cIWnCSnrACDV73r76dy0aQQ=
 github.com/leodido/go-urn v1.3.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+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.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -97,6 +113,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
 github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -114,6 +132,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@@ -124,6 +144,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
 github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
+github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
+github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
 github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
 github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
@@ -135,13 +157,19 @@ golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
 golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
 golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
+golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg=
+golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ=
 golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
 golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
+golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
diff --git a/huma.go b/huma.go
index 46184f0..799e378 100644
--- a/huma.go
+++ b/huma.go
@@ -10,6 +10,7 @@ package huma
 import (
 	"bytes"
 	"context"
+	"encoding"
 	"errors"
 	"fmt"
 	"io"
@@ -195,7 +196,7 @@ func findParams(registry Registry, op *Operation, t reflect.Type) *findResult[*p
 
 func findResolvers(resolverType, t reflect.Type) *findResult[bool] {
 	return findInType(t, func(t reflect.Type, path []int) bool {
-		tp := reflect.PtrTo(t)
+		tp := reflect.PointerTo(t)
 		if tp.Implements(resolverType) || tp.Implements(resolverWithPathType) {
 			return true
 		}
@@ -1051,6 +1052,16 @@ func Register[I, O any](api API, op Operation, handler func(context.Context, *I)
 						break
 					}
 
+					// Last resort: use the `encoding.TextUnmarshaler` interface.
+					if fn, ok := f.Addr().Interface().(encoding.TextUnmarshaler); ok {
+						if err := fn.UnmarshalText([]byte(value)); err != nil {
+							res.Add(pb, value, "invalid value: "+err.Error())
+							return
+						}
+						pv = value
+						break
+					}
+
 					panic("unsupported param type " + p.Type.String())
 				}
 
diff --git a/openapi.go b/openapi.go
index 3426d95..d593a97 100644
--- a/openapi.go
+++ b/openapi.go
@@ -3,7 +3,6 @@ package huma
 import (
 	"bytes"
 	"encoding/json"
-	"fmt"
 	"net/http"
 	"reflect"
 	"time"
@@ -1388,7 +1387,7 @@ func (t *Tag) MarshalJSON() ([]byte, error) {
 type AddOpFunc func(oapi *OpenAPI, op *Operation)
 
 // OpenAPI is the root object of the OpenAPI document.
-type OpenAPI struct { //nolint: musttag
+type OpenAPI struct {
 	// OpenAPI is REQUIRED. This string MUST be the version number of the OpenAPI
 	// Specification that the OpenAPI document uses. The openapi field SHOULD be
 	// used by tooling to interpret the OpenAPI document. This is not related to
@@ -1486,7 +1485,7 @@ func (o *OpenAPI) AddOperation(op *Operation) {
 	case http.MethodTrace:
 		item.Trace = op
 	default:
-		panic(fmt.Sprintf("unknown method %s", op.Method))
+		panic("unknown method " + op.Method)
 	}
 
 	for _, f := range o.OnAddOperation {
diff --git a/schema.go b/schema.go
index 1a24ff4..c6131ad 100644
--- a/schema.go
+++ b/schema.go
@@ -27,6 +27,7 @@ const (
 	TypeObject  = "object"
 )
 
+// Special JSON Schema formats.
 var (
 	timeType = reflect.TypeOf(time.Time{})
 	ipType   = reflect.TypeOf(net.IP{})
@@ -54,7 +55,7 @@ func deref(t reflect.Type) reflect.Type {
 //	schema := huma.SchemaFromType(registry, reflect.TypeOf(MyType{}))
 //
 // Note that the registry may create references for your types.
-type Schema struct { //nolint: musttag
+type Schema struct {
 	Type                 string             `yaml:"type,omitempty"`
 	Title                string             `yaml:"title,omitempty"`
 	Description          string             `yaml:"description,omitempty"`
@@ -431,8 +432,8 @@ func SchemaFromField(registry Registry, f reflect.StructField, hint string) *Sch
 		// tags later.
 		fs.Format = "date-time-http"
 	}
-	if fmt := f.Tag.Get("format"); fmt != "" {
-		fs.Format = fmt
+	if format := f.Tag.Get("format"); format != "" {
+		fs.Format = format
 	}
 	if timeFmt := f.Tag.Get("timeFormat"); timeFmt != "" {
 		switch timeFmt {
@@ -557,8 +558,13 @@ func SchemaFromType(r Registry, t reflect.Type) *Schema {
 	s := Schema{}
 	t = deref(t)
 
-	if t == ipType {
-		// Special case: IP address.
+	// Handle special cases.
+	switch t {
+	case timeType:
+		return &Schema{Type: TypeString, Format: "date-time"}
+	case urlType:
+		return &Schema{Type: TypeString, Format: "uri"}
+	case ipType:
 		return &Schema{Type: TypeString, Format: "ipv4"}
 	}
 
@@ -615,23 +621,15 @@ func SchemaFromType(r Registry, t reflect.Type) *Schema {
 			s.Items = r.Schema(t.Elem(), true, t.Name()+"Item")
 
 			if t.Kind() == reflect.Array {
-				len := t.Len()
-				s.MinItems = &len
-				s.MaxItems = &len
+				l := t.Len()
+				s.MinItems = &l
+				s.MaxItems = &l
 			}
 		}
 	case reflect.Map:
 		s.Type = TypeObject
 		s.AdditionalProperties = r.Schema(t.Elem(), true, t.Name()+"Value")
 	case reflect.Struct:
-		// Handle special cases.
-		switch t {
-		case timeType:
-			return &Schema{Type: TypeString, Format: "date-time"}
-		case urlType:
-			return &Schema{Type: TypeString, Format: "uri"}
-		}
-
 		required := []string{}
 		requiredMap := map[string]bool{}
 		propNames := []string{}
diff --git a/validate.go b/validate.go
index ef23808..b7f2293 100644
--- a/validate.go
+++ b/validate.go
@@ -802,7 +802,7 @@ func validateUUID(s string) error {
 	// UUID enclosed in braces
 	case 36 + 2:
 		if s[0] != '{' || s[len(s)-1] != '}' {
-			return fmt.Errorf("invalid bracketed UUID format")
+			return errors.New("invalid bracketed UUID format")
 		}
 		s = s[1 : len(s)-1]
 


Test patch:
diff --git a/adapters/humabunrouter/humabunrouter_test.go b/adapters/humabunrouter/humabunrouter_test.go
index 5b1a700..c61758c 100644
--- a/adapters/humabunrouter/humabunrouter_test.go
+++ b/adapters/humabunrouter/humabunrouter_test.go
@@ -3,6 +3,7 @@ package humabunrouter
 import (
 	"context"
 	"encoding/json"
+	"errors"
 	"fmt"
 	"io"
 	"net/http"
@@ -13,8 +14,9 @@ import (
 	"testing"
 	"time"
 
-	"github.com/danielgtaylor/huma/v2"
 	"github.com/uptrace/bunrouter"
+
+	"github.com/danielgtaylor/huma/v2"
 )
 
 var lastModified = time.Now()
@@ -284,7 +286,7 @@ func BenchmarkRawBunRouterFast(b *testing.B) {
 		}
 
 		if len(input.Suffix) > 5 {
-			return fmt.Errorf("suffix too long")
+			return errors.New("suffix too long")
 		}
 
 		w.Header().Set("Content-Type", "application/json")
diff --git a/autopatch/autopatch_test.go b/autopatch/autopatch_test.go
index 3909061..e9355de 100644
--- a/autopatch/autopatch_test.go
+++ b/autopatch/autopatch_test.go
@@ -2,15 +2,17 @@ package autopatch
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net/http"
 	"strings"
 	"testing"
 	"testing/iotest"
 
+	"github.com/stretchr/testify/assert"
+
 	"github.com/danielgtaylor/huma/v2"
 	"github.com/danielgtaylor/huma/v2/humatest"
-	"github.com/stretchr/testify/assert"
 )
 
 type SaleModel struct {
@@ -210,7 +212,7 @@ func TestPatch(t *testing.T) {
 	// PATCH body read error
 	w = api.Patch("/things/notfound",
 		"Content-Type: application/merge-patch+json",
-		iotest.ErrReader(fmt.Errorf("test error")),
+		iotest.ErrReader(errors.New("test error")),
 	)
 	assert.Equal(t, http.StatusBadRequest, w.Code, w.Body.String())
 
diff --git a/error_test.go b/error_test.go
index 42ecc44..d407b3b 100644
--- a/error_test.go
+++ b/error_test.go
@@ -1,15 +1,16 @@
 package huma_test
 
 import (
-	"fmt"
+	"errors"
 	"net/http"
 	"net/http/httptest"
 	"testing"
 
-	"github.com/danielgtaylor/huma/v2"
-	"github.com/danielgtaylor/huma/v2/humatest"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/danielgtaylor/huma/v2"
+	"github.com/danielgtaylor/huma/v2/humatest"
 )
 
 // Ensure the default error models satisfy these interfaces.
@@ -30,7 +31,7 @@ func TestError(t *testing.T) {
 		Value:    "bar",
 	})
 
-	err.Add(fmt.Errorf("plain error"))
+	err.Add(errors.New("plain error"))
 
 	// Confirm errors were added.
 	assert.Equal(t, "test err", err.Error())
@@ -89,7 +90,7 @@ func TestTransformError(t *testing.T) {
 	config := huma.DefaultConfig("Test API", "1.0.0")
 	config.Transformers = []huma.Transformer{
 		func(ctx huma.Context, status string, v any) (any, error) {
-			return nil, fmt.Errorf("whoops")
+			return nil, errors.New("whoops")
 		},
 	}
 	_, api := humatest.New(t, config)
diff --git a/huma_test.go b/huma_test.go
index df9e90c..88ad034 100644
--- a/huma_test.go
+++ b/huma_test.go
@@ -3,20 +3,23 @@ package huma_test
 import (
 	"context"
 	"encoding/json"
-	"fmt"
+	"errors"
 	"io"
+	"net"
 	"net/http"
 	"net/http/httptest"
 	"strings"
 	"testing"
 	"time"
 
-	"github.com/danielgtaylor/huma/v2"
-	"github.com/danielgtaylor/huma/v2/adapters/humachi"
-	"github.com/danielgtaylor/huma/v2/humatest"
 	"github.com/go-chi/chi/v5"
+	"github.com/google/uuid"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/danielgtaylor/huma/v2"
+	"github.com/danielgtaylor/huma/v2/adapters/humachi"
+	"github.com/danielgtaylor/huma/v2/humatest"
 )
 
 var NewExampleAdapter = humatest.NewAdapter
@@ -37,6 +40,15 @@ func Recoverer(next http.Handler) http.Handler {
 	return http.HandlerFunc(fn)
 }
 
+// UUID is a custom type for testing SchemaProvider
+type UUID struct {
+	uuid.UUID
+}
+
+func (UUID) Schema(r huma.Registry) *huma.Schema {
+	return &huma.Schema{Type: huma.TypeString, Format: "uuid"}
+}
+
 func TestFeatures(t *testing.T) {
 	for _, feature := range []struct {
 		Name         string
@@ -103,10 +115,11 @@ func TestFeatures(t *testing.T) {
 			Register: func(t *testing.T, api huma.API) {
 				huma.Register(api, huma.Operation{
 					Method: http.MethodGet,
-					Path:   "/test-params/{string}/{int}",
+					Path:   "/test-params/{string}/{int}/{uuid}",
 				}, func(ctx context.Context, input *struct {
 					PathString   string    `path:"string"`
 					PathInt      int       `path:"int"`
+					PathUUID     UUID      `path:"uuid"`
 					QueryString  string    `query:"string"`
 					QueryInt     int       `query:"int"`
 					QueryDefault float32   `query:"def" default:"135" example:"5"`
@@ -136,9 +149,10 @@ func TestFeatures(t *testing.T) {
 				}) (*struct{}, error) {
 					assert.Equal(t, "foo", input.PathString)
 					assert.Equal(t, 123, input.PathInt)
+					assert.Equal(t, UUID{UUID: uuid.MustParse("fba4f46b-4539-4d19-8e3f-a0e629a243b5")}, input.PathUUID)
 					assert.Equal(t, "bar", input.QueryString)
 					assert.Equal(t, 456, input.QueryInt)
-					assert.EqualValues(t, 135, input.QueryDefault)
+					assert.InDelta(t, 135, input.QueryDefault, 0)
 					assert.True(t, input.QueryBefore.Equal(time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC)))
 					assert.True(t, input.QueryDate.Equal(time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)))
 					assert.EqualValues(t, 1, input.QueryUint)
@@ -164,10 +178,10 @@ func TestFeatures(t *testing.T) {
 				})
 
 				// `http.Cookie` should be treated as a string.
-				assert.Equal(t, "string", api.OpenAPI().Paths["/test-params/{string}/{int}"].Get.Parameters[26].Schema.Type)
+				assert.Equal(t, "string", api.OpenAPI().Paths["/test-params/{string}/{int}/{uuid}"].Get.Parameters[27].Schema.Type)
 			},
 			Method: http.MethodGet,
-			URL:    "/test-params/foo/123?string=bar&int=456&before=2023-01-01T12:00:00Z&date=2023-01-01&uint=1&bool=true&strings=foo,bar&ints=2,3&ints8=4,5&ints16=4,5&ints32=4,5&ints64=4,5&uints=1,2&uints16=10,15&uints32=10,15&uints64=10,15&floats32=2.2,2.3&floats64=3.2,3.3",
+			URL:    "/test-params/foo/123/fba4f46b-4539-4d19-8e3f-a0e629a243b5?string=bar&int=456&before=2023-01-01T12:00:00Z&date=2023-01-01&uint=1&bool=true&strings=foo,bar&ints=2,3&ints8=4,5&ints16=4,5&ints32=4,5&ints64=4,5&uints=1,2&uints16=10,15&uints32=10,15&uints64=10,15&floats32=2.2,2.3&floats64=3.2,3.3",
 			Headers: map[string]string{
 				"string": "baz",
 				"int":    "789",
@@ -180,9 +194,10 @@ func TestFeatures(t *testing.T) {
 			Register: func(t *testing.T, api huma.API) {
 				huma.Register(api, huma.Operation{
 					Method: http.MethodGet,
-					Path:   "/test-params/{int}",
+					Path:   "/test-params/{int}/{uuid}",
 				}, func(ctx context.Context, input *struct {
 					PathInt       string    `path:"int"`
+					PathUUID      UUID      `path:"uuid"`
 					QueryInt      int       `query:"int"`
 					QueryFloat    float32   `query:"float"`
 					QueryBefore   time.Time `query:"before"`
@@ -207,11 +222,12 @@ func TestFeatures(t *testing.T) {
 				})
 			},
 			Method: http.MethodGet,
-			URL:    "/test-params/bad?int=bad&float=bad&before=bad&date=bad&uint=bad&bool=bad&ints=bad&ints8=bad&ints16=bad&ints32=bad&ints64=bad&uints=bad&uints16=bad&uints32=bad&uints64=bad&floats32=bad&floats64=bad",
+			URL:    "/test-params/bad/not-a-uuid?int=bad&float=bad&before=bad&date=bad&uint=bad&bool=bad&ints=bad&ints8=bad&ints16=bad&ints32=bad&ints64=bad&uints=bad&uints16=bad&uints32=bad&uints64=bad&floats32=bad&floats64=bad",
 			Assert: func(t *testing.T, resp *httptest.ResponseRecorder) {
 				assert.Equal(t, http.StatusUnprocessableEntity, resp.Code)
 
 				assert.Contains(t, resp.Body.String(), "invalid integer")
+				assert.Contains(t, resp.Body.String(), "invalid value: invalid UUID length: 10")
 				assert.Contains(t, resp.Body.String(), "invalid float")
 				assert.Contains(t, resp.Body.String(), "invalid date/time")
 				assert.Contains(t, resp.Body.String(), "invalid bool")
@@ -231,6 +247,24 @@ func TestFeatures(t *testing.T) {
 				assert.Contains(t, resp.Body.String(), "query.floats64")
 			},
 		},
+		{
+			Name: "param-unsupported-500",
+			Register: func(t *testing.T, api huma.API) {
+				huma.Register(api, huma.Operation{
+					Method: http.MethodGet,
+					Path:   "/test-params/{ipnet}",
+				}, func(ctx context.Context, input *struct {
+					PathIPNet net.IPNet `path:"ipnet"`
+				}) (*struct{}, error) {
+					return nil, nil
+				})
+			},
+			Method: http.MethodGet,
+			URL:    "/test-params/255.255.0.0",
+			Assert: func(t *testing.T, resp *httptest.ResponseRecorder) {
+				assert.Equal(t, http.StatusInternalServerError, resp.Code)
+			},
+		},
 		{
 			Name: "request-body",
 			Register: func(t *testing.T, api huma.API) {
@@ -670,7 +704,7 @@ func TestFeatures(t *testing.T) {
 			Name: "response-transform-error",
 			Transformers: []huma.Transformer{
 				func(ctx huma.Context, status string, v any) (any, error) {
-					return nil, fmt.Errorf("whoops")
+					return nil, errors.New("whoops")
 				},
 			},
 			Register: func(t *testing.T, api huma.API) {
@@ -902,7 +936,7 @@ type ExhaustiveErrorsInputBody struct {
 }
 
 func (b *ExhaustiveErrorsInputBody) Resolve(ctx huma.Context) []error {
-	return []error{fmt.Errorf("body resolver error")}
+	return []error{errors.New("body resolver error")}
 }
 
 type ExhaustiveErrorsInput struct {
@@ -1021,7 +1055,7 @@ func TestCustomError(t *testing.T) {
 		Method:      http.MethodGet,
 		Path:        "/error",
 	}, func(ctx context.Context, i *struct{}) (*struct{}, error) {
-		return nil, huma.Error404NotFound("not found", fmt.Errorf("some-other-error"))
+		return nil, huma.Error404NotFound("not found", errors.New("some-other-error"))
 	})
 
 	resp := api.Get("/error", "Host: localhost")
@@ -1123,7 +1157,7 @@ func TestResolverCompositionCalledOnce(t *testing.T) {
 }
 
 func TestParamPointerPanics(t *testing.T) {
-	// For now we don't support these, so we panic rather than have subtle
+	// For now, we don't support these, so we panic rather than have subtle
 	// bugs that are hard to track down.
 	_, app := humatest.New(t, huma.DefaultConfig("Test API", "1.0.0"))
 
@@ -1141,7 +1175,7 @@ func TestParamPointerPanics(t *testing.T) {
 }
 
 func TestPointerDefaultPanics(t *testing.T) {
-	// For now we don't support these, so we panic rather than have subtle
+	// For now, we don't support these, so we panic rather than have subtle
 	// bugs that are hard to track down.
 	_, app := humatest.New(t, huma.DefaultConfig("Test API", "1.0.0"))
 
diff --git a/schema_test.go b/schema_test.go
index b6ba683..82a9076 100644
--- a/schema_test.go
+++ b/schema_test.go
@@ -11,9 +11,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/danielgtaylor/huma/v2"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/danielgtaylor/huma/v2"
 )
 
 type EmbeddedChild struct {
@@ -701,7 +702,7 @@ func TestSchemaGenericNamingFromModule(t *testing.T) {
 	}`, string(b))
 }
 
-type OmittableNullable[T any] struct { //nolint: musttag
+type OmittableNullable[T any] struct {
 	Sent  bool
 	Null  bool
 	Value T
@@ -735,25 +736,25 @@ func TestCustomUnmarshalType(t *testing.T) {
 	s := r.Schema(reflect.TypeOf(o), false, "")
 	assert.Equal(t, "integer", s.Properties["field"].Type, s)
 	assert.Equal(t, Ptr(float64(10)), s.Properties["field"].Maximum, s)
-	assert.Equal(t, float64(5), s.Properties["field"].Examples[0], s.Properties["field"])
+	assert.InDelta(t, float64(5), s.Properties["field"].Examples[0], 0, s.Properties["field"])
 
 	// Confirm the field works as expected when loading JSON.
 	o = O{}
-	err := json.Unmarshal([]byte(`{"field": 123}`), &o)
+	err := json.Unmarshal([]byte(`{"field": 123}`), &o) //nolint:musttag
 	require.NoError(t, err)
 	assert.True(t, o.Field.Sent)
 	assert.False(t, o.Field.Null)
 	assert.Equal(t, 123, o.Field.Value)
 
 	o = O{}
-	err = json.Unmarshal([]byte(`{"field": null}`), &o)
+	err = json.Unmarshal([]byte(`{"field": null}`), &o) //nolint:musttag
 	require.NoError(t, err)
 	assert.True(t, o.Field.Sent)
 	assert.True(t, o.Field.Null)
 	assert.Equal(t, 0, o.Field.Value)
 
 	o = O{}
-	err = json.Unmarshal([]byte(`{}`), &o)
+	err = json.Unmarshal([]byte(`{}`), &o) //nolint:musttag
 	require.NoError(t, err)
 	assert.False(t, o.Field.Sent)
 	assert.False(t, o.Field.Null)
diff --git a/sse/sse_test.go b/sse/sse_test.go
index 384c38e..4cfc46c 100644
--- a/sse/sse_test.go
+++ b/sse/sse_test.go
@@ -2,16 +2,17 @@ package sse_test
 
 import (
 	"context"
-	"fmt"
+	"errors"
 	"net/http"
 	"testing"
 	"time"
 
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
 	"github.com/danielgtaylor/huma/v2"
 	"github.com/danielgtaylor/huma/v2/humatest"
 	"github.com/danielgtaylor/huma/v2/sse"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
 )
 
 type DefaultMessage struct {
@@ -96,7 +97,7 @@ data: {"error": "encode error: json: unsupported type: chan int"}
 `, resp.Body.String())
 
 	// Test write error doens't panic
-	w := &DummyWriter{writeErr: fmt.Errorf("whoops")}
+	w := &DummyWriter{writeErr: errors.New("whoops")}
 	req, _ := http.NewRequest(http.MethodGet, "/sse", nil)
 	api.Adapter().ServeHTTP(w, req)