From aacfa051da449fa404be1a5464b9099856af932a Mon Sep 17 00:00:00 2001 From: Jonathan Vuillemin Date: Sun, 10 Nov 2024 22:51:40 +0100 Subject: [PATCH] feat(fxhttpserver): Added possibility to specify several HTTP methods during handlers registration (#294) --- fxhttpserver/.golangci.yml | 2 +- fxhttpserver/README.md | 12 ++ fxhttpserver/go.mod | 42 +++---- fxhttpserver/go.sum | 81 +++++++------ fxhttpserver/method.go | 50 ++++++++ fxhttpserver/method_test.go | 120 +++++++++++++++++++ fxhttpserver/module.go | 58 +++++---- fxhttpserver/module_test.go | 230 +++++++++++++++++++++++++++++++++++- fxhttpserver/util.go | 11 ++ 9 files changed, 523 insertions(+), 83 deletions(-) create mode 100644 fxhttpserver/method.go create mode 100644 fxhttpserver/method_test.go diff --git a/fxhttpserver/.golangci.yml b/fxhttpserver/.golangci.yml index 6812f585..e6626676 100644 --- a/fxhttpserver/.golangci.yml +++ b/fxhttpserver/.golangci.yml @@ -38,7 +38,7 @@ linters: - importas - ineffassign - interfacebloat - - logrlint + - loggercheck - maintidx - makezero - misspell diff --git a/fxhttpserver/README.md b/fxhttpserver/README.md index 2f5c3715..8b4fd742 100644 --- a/fxhttpserver/README.md +++ b/fxhttpserver/README.md @@ -304,6 +304,12 @@ func main() { } ``` +Notes: + +- you can specify several valid HTTP methods (comma separated) while registering a handler, for example `fxhttpserver.AsHandler("GET,POST", ...)` +- you can use the shortcut `*` to register a handler for all valid HTTP methods, for example `fxhttpserver.AsHandler("*", ...)` +- valid HTTP methods are `CONNECT`, `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, `PUT`, `TRACE`, `PROPFIND` and `REPORT` + #### Handlers groups You can use the `AsHandlersGroup()` function to register handlers groups and their middlewares on your http @@ -427,6 +433,12 @@ func main() { } ``` +Notes: + +- you can specify several valid HTTP methods (comma separated) while registering a handler in a group, for example `fxhttpserver.NewHandlerRegistration("GET,POST", ...)` +- you can use the shortcut `*` to register a handler for all valid HTTP methods, for example `fxhttpserver.NewHandlerRegistration("*", ...)` +- valid HTTP methods are `CONNECT`, `DELETE`, `GET`, `HEAD`, `OPTIONS`, `PATCH`, `POST`, `PUT`, `TRACE`, `PROPFIND` and `REPORT` + ### WebSocket This module supports the `WebSocket` protocol, see the [Echo documentation](https://echo.labstack.com/docs/cookbook/websocket) for more information. diff --git a/fxhttpserver/go.mod b/fxhttpserver/go.mod index 8caba685..829b64b5 100644 --- a/fxhttpserver/go.mod +++ b/fxhttpserver/go.mod @@ -3,28 +3,29 @@ module github.com/ankorstore/yokai/fxhttpserver go 1.20 require ( - github.com/ankorstore/yokai/config v1.3.0 - github.com/ankorstore/yokai/fxconfig v1.1.0 - github.com/ankorstore/yokai/fxgenerate v1.1.0 + github.com/ankorstore/yokai/config v1.5.0 + github.com/ankorstore/yokai/fxconfig v1.3.0 + github.com/ankorstore/yokai/fxgenerate v1.2.0 github.com/ankorstore/yokai/fxlog v1.1.0 github.com/ankorstore/yokai/fxmetrics v1.2.0 github.com/ankorstore/yokai/fxtrace v1.2.0 - github.com/ankorstore/yokai/generate v1.1.0 + github.com/ankorstore/yokai/generate v1.2.0 github.com/ankorstore/yokai/httpserver v1.5.0 github.com/ankorstore/yokai/log v1.2.0 github.com/ankorstore/yokai/trace v1.3.0 - github.com/labstack/echo/v4 v4.11.4 - github.com/prometheus/client_golang v1.19.0 + github.com/labstack/echo/v4 v4.12.0 + github.com/labstack/gommon v0.4.2 + github.com/prometheus/client_golang v1.20.5 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/trace v1.24.0 - go.uber.org/fx v1.21.0 + go.uber.org/fx v1.23.0 ) require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect @@ -35,16 +36,17 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/labstack/gommon v0.4.2 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.13.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rs/zerolog v1.32.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -52,7 +54,7 @@ require ( github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.18.2 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect @@ -63,19 +65,19 @@ require ( go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/sdk v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect - go.uber.org/dig v1.17.1 // indirect + go.uber.org/dig v1.18.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.21.0 // indirect + golang.org/x/crypto v0.24.0 // indirect golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect google.golang.org/grpc v1.62.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/fxhttpserver/go.sum b/fxhttpserver/go.sum index b3704f62..72f309f2 100644 --- a/fxhttpserver/go.sum +++ b/fxhttpserver/go.sum @@ -1,17 +1,17 @@ -github.com/ankorstore/yokai/config v1.3.0 h1:si2h4mESPN5pj14CBMT/VGFgFn0voKEVylr8hQeIgEk= -github.com/ankorstore/yokai/config v1.3.0/go.mod h1:OV2QiL2dyNLCxhcGO+GcSa8Wm20+00H03VBHm9SPVuE= -github.com/ankorstore/yokai/fxconfig v1.1.0 h1:QgRDrZPpSy4wlnzNN37sWniRRAszerBb6WpvMa3hTB0= -github.com/ankorstore/yokai/fxconfig v1.1.0/go.mod h1:dU8W3eJtioegWEB7X5C+B40Ud+M+vRa5d2UdbAJr9Os= -github.com/ankorstore/yokai/fxgenerate v1.1.0 h1:kNEAsZJt7DgwVpG9+/M7p8610BdKHWkX/MYdBLuSIGU= -github.com/ankorstore/yokai/fxgenerate v1.1.0/go.mod h1:trC9VZEaVjTXuO9GWbqVPGKFkKULDZpLoxdTyTmmpYU= +github.com/ankorstore/yokai/config v1.5.0 h1:vL/l0dcnq34FtxE+Up1NvzgcRB0G/vI4Yo/H5PccfN0= +github.com/ankorstore/yokai/config v1.5.0/go.mod h1:C8ggYvcrG+J0Ra2vTtcDCANa8HMf3FdrC0Ek8o3tTEw= +github.com/ankorstore/yokai/fxconfig v1.3.0 h1:kk+RkpgECjZYciN2E3lnVj1dpewRy54JN7k8zErpX88= +github.com/ankorstore/yokai/fxconfig v1.3.0/go.mod h1:NTF2TbT+xZNEzI/iTCQLtY+oS/AJSDAPAqouPgAYzbE= +github.com/ankorstore/yokai/fxgenerate v1.2.0 h1:Fnw0DauFbuFwpKNVliKlZbvLC1Xg9Af0lxQCRkbvfLo= +github.com/ankorstore/yokai/fxgenerate v1.2.0/go.mod h1:cTn+S3Wk3rql/KRVtOXn4kQyMAYpi5n1rcXisWR9uks= github.com/ankorstore/yokai/fxlog v1.1.0 h1:vLI8Qd9KfCzAH9IvzGJTvFYmlE1jtMnjvA4z/vxJpYg= github.com/ankorstore/yokai/fxlog v1.1.0/go.mod h1:VHlj/FNGAuLNqTyRCCx3iGUi9IZXv7qVNrDLUQng1cE= github.com/ankorstore/yokai/fxmetrics v1.2.0 h1:B4vwfOxsUeFXC5rn0bDHsFnOhEFhRq9aUEWpEayEOCY= github.com/ankorstore/yokai/fxmetrics v1.2.0/go.mod h1:WBr76IIdlSZIpBsjKSdXCAJBWF0HCp46bwFX8bt0tFk= github.com/ankorstore/yokai/fxtrace v1.2.0 h1:SXlWbjKSsb2wVH+hXSE9OD2VwyqkznwwW+kiQcNvEAU= github.com/ankorstore/yokai/fxtrace v1.2.0/go.mod h1:ch72eVTlIedETOApK7SXk2NEWpn3yYeM018dNRccocg= -github.com/ankorstore/yokai/generate v1.1.0 h1:tu3S+uEYh+2qNo8Rf/WxWneDjh49YgDPzSnJfF8JkXA= -github.com/ankorstore/yokai/generate v1.1.0/go.mod h1:gqS/i20wnvCOhcXydYdiGcASzBaeuW7GK6YYg/kkuY4= +github.com/ankorstore/yokai/generate v1.2.0 h1:37siukjPGSS2kRnCnPhiuiF373+0tgwp0teXHnMsBhA= +github.com/ankorstore/yokai/generate v1.2.0/go.mod h1:gqS/i20wnvCOhcXydYdiGcASzBaeuW7GK6YYg/kkuY4= github.com/ankorstore/yokai/httpserver v1.5.0 h1:42nfCFCGWuBKbwU8Jhlf1/ofrezDes8HlCa0mhiVoI8= github.com/ankorstore/yokai/httpserver v1.5.0/go.mod h1:AOCL4cK2bPKrtGFULvOvc8mKHAOw2bLW30CKJra2BB0= github.com/ankorstore/yokai/log v1.2.0 h1:jiuDiC0dtqIGIOsFQslUHYoFJ1qjI+rOMa6dI1LBf2Y= @@ -22,8 +22,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -51,10 +51,13 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0Q github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= -github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= +github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -67,20 +70,22 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= -github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= -github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= -github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= @@ -97,8 +102,8 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -131,28 +136,28 @@ go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= -go.uber.org/dig v1.17.1 h1:Tga8Lz8PcYNsWsyHMZ1Vm0OQOUaJNDyvPImgbAu9YSc= -go.uber.org/dig v1.17.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= -go.uber.org/fx v1.21.0 h1:qqD6k7PyFHONffW5speYx403ywanuASqU4Rqdpc22XY= -go.uber.org/fx v1.21.0/go.mod h1:HT2M7d7RHo+ebKGh9NRcrsrHHfpZ60nW3QRubMRfv48= +go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= +go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= +go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg= +go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw= golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.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= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc= @@ -161,8 +166,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= diff --git a/fxhttpserver/method.go b/fxhttpserver/method.go new file mode 100644 index 00000000..03c8da25 --- /dev/null +++ b/fxhttpserver/method.go @@ -0,0 +1,50 @@ +package fxhttpserver + +import ( + "fmt" + "net/http" + "strings" +) + +const ( + // MethodPropfind can be used on collection and property resources. + MethodPropfind = "PROPFIND" + // MethodReport Method can be used to get information about a resource, see rfc 3253 + MethodReport = "REPORT" + // AllMethods is a shortcut to specify all valid methods. + AllMethods = "*" +) + +var validMethods = []string{ + http.MethodConnect, + http.MethodDelete, + http.MethodGet, + http.MethodHead, + http.MethodOptions, + http.MethodPatch, + http.MethodPost, + http.MethodPut, + http.MethodTrace, + MethodPropfind, + MethodReport, +} + +func ExtractMethods(methods string) ([]string, error) { + if methods == AllMethods { + return validMethods, nil + } + + var extractedMethods []string + + for _, method := range Split(methods) { + method = strings.ToUpper(method) + + if Contains(validMethods, method) { + extractedMethods = append(extractedMethods, method) + } else { + return nil, fmt.Errorf("invalid HTTP method %q", method) + } + } + + return extractedMethods, nil +} diff --git a/fxhttpserver/method_test.go b/fxhttpserver/method_test.go new file mode 100644 index 00000000..088775bd --- /dev/null +++ b/fxhttpserver/method_test.go @@ -0,0 +1,120 @@ +package fxhttpserver_test + +import ( + "testing" + + "github.com/ankorstore/yokai/fxhttpserver" + "github.com/stretchr/testify/assert" +) + +func TestExtractMethods(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + input string + expectedOutput []string + expectedError string + }{ + "with CONNECT": { + input: "CONNECT", + expectedOutput: []string{"CONNECT"}, + }, + "with GET": { + input: "GET", + expectedOutput: []string{"GET"}, + }, + "with POST": { + input: "POST", + expectedOutput: []string{"POST"}, + }, + "with PUT": { + input: "PUT", + expectedOutput: []string{"PUT"}, + }, + "with PATCH": { + input: "PATCH", + expectedOutput: []string{"PATCH"}, + }, + "with DELETE": { + input: "DELETE", + expectedOutput: []string{"DELETE"}, + }, + "with HEAD": { + input: "HEAD", + expectedOutput: []string{"HEAD"}, + }, + "with OPTIONS": { + input: "OPTIONS", + expectedOutput: []string{"OPTIONS"}, + }, + "with TRACE": { + input: "TRACE", + expectedOutput: []string{"TRACE"}, + }, + "with PROPFIND": { + input: "PROPFIND", + expectedOutput: []string{"PROPFIND"}, + }, + "with REPORT": { + input: "REPORT", + expectedOutput: []string{"REPORT"}, + }, + "with get": { + input: "get", + expectedOutput: []string{"GET"}, + }, + "with GET,POST": { + input: "GET,POST", + expectedOutput: []string{"GET", "POST"}, + }, + "with GET , POST ": { + input: " GET , POST ", + expectedOutput: []string{"GET", "POST"}, + }, + "with get,post": { + input: "get,post", + expectedOutput: []string{"GET", "POST"}, + }, + "with *": { + input: "*", + expectedOutput: []string{ + "CONNECT", + "DELETE", + "GET", + "HEAD", + "OPTIONS", + "PATCH", + "POST", + "PUT", + "TRACE", + "PROPFIND", + "REPORT", + }, + }, + "with invalid": { + input: "invalid", + expectedOutput: nil, + expectedError: `invalid HTTP method "INVALID"`, + }, + "with get,invalid,POST": { + input: "get,invalid,POST", + expectedOutput: nil, + expectedError: `invalid HTTP method "INVALID"`, + }, + } + + for tn, tt := range tests { + tt := tt + + t.Run(tn, func(t *testing.T) { + t.Parallel() + + output, err := fxhttpserver.ExtractMethods(tt.input) + if err != nil { + assert.Equal(t, tt.expectedError, err.Error()) + } + + assert.Equal(t, tt.expectedOutput, output) + }) + } +} diff --git a/fxhttpserver/module.go b/fxhttpserver/module.go index 7940986b..cd050eda 100644 --- a/fxhttpserver/module.go +++ b/fxhttpserver/module.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "strconv" - "strings" "github.com/ankorstore/yokai/config" "github.com/ankorstore/yokai/generate/uuid" @@ -86,11 +85,14 @@ func NewFxHttpServer(p FxHttpServerParam) (*echo.Echo, error) { return nil, fmt.Errorf("failed to create http server: %w", err) } - // middlewares + // middlewares registrations httpServer = withDefaultMiddlewares(httpServer, p) // groups, handlers & middlewares registrations - httpServer = withRegisteredResources(httpServer, p) + httpServer, err = withRegisteredResources(httpServer, p) + if err != nil { + return httpServer, fmt.Errorf("failed to register http server resources: %w", err) + } // lifecycles p.LifeCycle.Append(fx.Hook{ @@ -191,7 +193,8 @@ func withDefaultMiddlewares(httpServer *echo.Echo, p FxHttpServerParam) *echo.Ec return httpServer } -func withRegisteredResources(httpServer *echo.Echo, p FxHttpServerParam) *echo.Echo { +//nolint:cyclop +func withRegisteredResources(httpServer *echo.Echo, p FxHttpServerParam) (*echo.Echo, error) { // register handler groups resolvedHandlersGroups, err := p.Registry.ResolveHandlersGroups() if err != nil { @@ -202,13 +205,21 @@ func withRegisteredResources(httpServer *echo.Echo, p FxHttpServerParam) *echo.E group := httpServer.Group(g.Prefix(), g.Middlewares()...) for _, h := range g.Handlers() { - group.Add( - strings.ToUpper(h.Method()), - h.Path(), - h.Handler(), - h.Middlewares()..., - ) - httpServer.Logger.Debugf("registering handler in group for [%s] %s%s", h.Method(), g.Prefix(), h.Path()) + methods, err := ExtractMethods(h.Method()) + if err != nil { + return httpServer, err + } + + for _, method := range methods { + group.Add( + method, + h.Path(), + h.Handler(), + h.Middlewares()..., + ) + + httpServer.Logger.Debugf("registering handler in group for [%s] %s%s", method, g.Prefix(), h.Path()) + } } httpServer.Logger.Debugf("registered handlers group for prefix %s", g.Prefix()) @@ -239,15 +250,22 @@ func withRegisteredResources(httpServer *echo.Echo, p FxHttpServerParam) *echo.E } for _, h := range resolvedHandlers { - httpServer.Add( - strings.ToUpper(h.Method()), - h.Path(), - h.Handler(), - h.Middlewares()..., - ) - - httpServer.Logger.Debugf("registered handler for [%s] %s", h.Method(), h.Path()) + methods, err := ExtractMethods(h.Method()) + if err != nil { + return httpServer, err + } + + for _, method := range methods { + httpServer.Add( + method, + h.Path(), + h.Handler(), + h.Middlewares()..., + ) + + httpServer.Logger.Debugf("registered handler for [%s] %s", h.Method(), h.Path()) + } } - return httpServer + return httpServer, nil } diff --git a/fxhttpserver/module_test.go b/fxhttpserver/module_test.go index 3bfaf8a0..e38c23a7 100644 --- a/fxhttpserver/module_test.go +++ b/fxhttpserver/module_test.go @@ -16,6 +16,7 @@ import ( "github.com/ankorstore/yokai/fxhttpserver/testdata/service" "github.com/ankorstore/yokai/fxlog" "github.com/ankorstore/yokai/fxmetrics" + "github.com/ankorstore/yokai/fxmetrics/testdata/spy" "github.com/ankorstore/yokai/fxtrace" "github.com/ankorstore/yokai/httpserver" "github.com/ankorstore/yokai/log" @@ -96,12 +97,12 @@ func TestModuleWithAutowiredResources(t *testing.T) { fx.Options( fxhttpserver.AsMiddleware(middleware.NewTestGlobalMiddleware, fxhttpserver.GlobalUse), fxhttpserver.AsHandler("GET", "/bar", handler.NewTestBarHandler, middleware.NewTestHandlerMiddleware), - fxhttpserver.AsHandler("GET", "/baz", handler.NewTestBazHandler, middleware.NewTestHandlerMiddleware), + fxhttpserver.AsHandler("GET,POST", "/baz", handler.NewTestBazHandler, middleware.NewTestHandlerMiddleware), fxhttpserver.AsHandlersGroup( "/foo", []*fxhttpserver.HandlerRegistration{ fxhttpserver.NewHandlerRegistration("GET", "/bar", handler.NewTestBarHandler, middleware.NewTestHandlerMiddleware), - fxhttpserver.NewHandlerRegistration("GET", "/baz", handler.NewTestBazHandler, middleware.NewTestHandlerMiddleware), + fxhttpserver.NewHandlerRegistration("GET,POST", "/baz", handler.NewTestBazHandler, middleware.NewTestHandlerMiddleware), }, middleware.NewTestGroupMiddleware, ), @@ -259,6 +260,81 @@ func TestModuleWithAutowiredResources(t *testing.T) { attribute.String(httpserver.TraceSpanAttributeHttpRequestId, testRequestId), ) + // [POST] /baz + req = httptest.NewRequest(http.MethodPost, "/baz", nil) + req.Header.Add("x-request-id", testRequestId) + req.Header.Add("traceparent", testTraceParent) + req.Header.Add("x-foo", "foo") + req.Header.Add("x-bar", "bar") + rec = httptest.NewRecorder() + httpServer.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + assert.Contains(t, rec.Body.String(), "baz: test") + assert.Equal(t, "true", rec.Header().Get("global-middleware")) + assert.Equal(t, "", rec.Header().Get("group-middleware")) + assert.Equal(t, "true", rec.Header().Get("handler-middleware")) + + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "GLOBAL middleware for app: test", + }) + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "HANDLER middleware for app: test", + }) + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "in baz handler", + }) + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "method": "POST", + "uri": "/baz", + "status": 200, + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "request logger", + }) + + tracetest.AssertHasTraceSpan( + t, + traceExporter, + "baz span", + attribute.String(httpserver.TraceSpanAttributeHttpRequestId, testRequestId), + ) + tracetest.AssertHasTraceSpan( + t, + traceExporter, + "POST /baz", + semconv.HTTPMethod(http.MethodPost), + semconv.HTTPRoute("/baz"), + semconv.HTTPStatusCode(http.StatusOK), + attribute.String(httpserver.TraceSpanAttributeHttpRequestId, testRequestId), + ) + // [GET] /foo/bar req = httptest.NewRequest(http.MethodGet, "/foo/bar", nil) req.Header.Add("x-request-id", testRequestId) @@ -431,6 +507,92 @@ func TestModuleWithAutowiredResources(t *testing.T) { ), ) + // [POST] /foo/baz + req = httptest.NewRequest(http.MethodPost, "/foo/baz", nil) + req.Header.Add("x-request-id", testRequestId) + req.Header.Add("traceparent", testTraceParent) + req.Header.Add("x-foo", "foo") + req.Header.Add("x-bar", "bar") + rec = httptest.NewRecorder() + httpServer.ServeHTTP(rec, req) + + assert.Equal(t, http.StatusOK, rec.Code) + assert.Contains(t, rec.Body.String(), "baz: test") + assert.Equal(t, "true", rec.Header().Get("global-middleware")) + assert.Equal(t, "true", rec.Header().Get("group-middleware")) + assert.Equal(t, "true", rec.Header().Get("handler-middleware")) + + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "GLOBAL middleware for app: test", + }) + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "GROUP middleware for app: test", + }) + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "HANDLER middleware for app: test", + }) + logtest.AssertHasLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "in baz handler", + }) + logtest.AssertHasNotLogRecord(t, logBuffer, map[string]interface{}{ + "level": "info", + "service": "test", + "module": "httpserver", + "method": "POST", + "uri": "/foo/baz", + "status": 200, + "requestID": testRequestId, + "traceID": testTraceId, + "foo": "foo", + "bar": "bar", + "message": "request logger", + }) + + tracetest.AssertHasTraceSpan( + t, + traceExporter, + "baz span", + attribute.String(httpserver.TraceSpanAttributeHttpRequestId, testRequestId), + ) + assert.False( + t, + traceExporter.HasSpan( + "POST /foo/baz", + semconv.HTTPMethod(http.MethodPost), + semconv.HTTPRoute("/foo/baz"), + semconv.HTTPStatusCode(http.StatusOK), + attribute.String(httpserver.TraceSpanAttributeHttpRequestId, testRequestId), + ), + ) + // [GET] /invalid req = httptest.NewRequest(http.MethodGet, "/invalid", nil) req.Header.Add("x-request-id", testRequestId) @@ -482,11 +644,11 @@ func TestModuleWithConcreteResources(t *testing.T) { fx.Provide(service.NewTestService), fx.Options( fxhttpserver.AsMiddleware(concreteGlobalMiddleware, fxhttpserver.GlobalUse), - fxhttpserver.AsHandler("GET", "/concrete", concreteHandler, concreteHandlerMiddleware), + fxhttpserver.AsHandler("*", "/concrete", concreteHandler, concreteHandlerMiddleware), fxhttpserver.AsHandlersGroup( "/group", []*fxhttpserver.HandlerRegistration{ - fxhttpserver.NewHandlerRegistration("GET", "/concrete", concreteHandler, concreteHandlerMiddleware), + fxhttpserver.NewHandlerRegistration("*", "/concrete", concreteHandler, concreteHandlerMiddleware), }, concreteGroupMiddleware, ), @@ -983,6 +1145,66 @@ func TestModuleWithTemplates(t *testing.T) { ) } +func TestModuleWithInvalidHandlerMethods(t *testing.T) { + t.Setenv("APP_CONFIG_PATH", "testdata/config") + t.Setenv("APP_DEBUG", "true") + + var httpServer *echo.Echo + + spyTB := spy.NewSpyTB() + + fxtest.New( + spyTB, + fx.NopLogger, + fxconfig.FxConfigModule, + fxlog.FxLogModule, + fxtrace.FxTraceModule, + fxmetrics.FxMetricsModule, + fxgenerate.FxGenerateModule, + fxhttpserver.FxHttpServerModule, + fx.Options( + fxhttpserver.AsHandler("invalid", "/concrete", concreteHandler), + ), + fx.Populate(&httpServer), + ).RequireStart().RequireStop() + + assert.NotZero(t, spyTB.Failures()) + assert.Contains(t, spyTB.Errors().String(), `failed to register http server resources: invalid HTTP method "INVALID"`) +} + +func TestModuleWithInvalidHandlersGroupMethods(t *testing.T) { + t.Setenv("APP_CONFIG_PATH", "testdata/config") + t.Setenv("APP_DEBUG", "true") + + var httpServer *echo.Echo + + spyTB := spy.NewSpyTB() + + fxtest.New( + spyTB, + fx.NopLogger, + fxconfig.FxConfigModule, + fxlog.FxLogModule, + fxtrace.FxTraceModule, + fxmetrics.FxMetricsModule, + fxgenerate.FxGenerateModule, + fxhttpserver.FxHttpServerModule, + fx.Options( + fxhttpserver.AsHandlersGroup( + "/group", + []*fxhttpserver.HandlerRegistration{ + fxhttpserver.NewHandlerRegistration("invalid", "/concrete", concreteHandler, concreteHandlerMiddleware), + }, + concreteGroupMiddleware, + ), + ), + fx.Populate(&httpServer), + ).RequireStart().RequireStop() + + assert.NotZero(t, spyTB.Failures()) + assert.Contains(t, spyTB.Errors().String(), `failed to register http server resources: invalid HTTP method "INVALID"`) +} + func TestModuleDecoration(t *testing.T) { t.Setenv("APP_CONFIG_PATH", "testdata/config") diff --git a/fxhttpserver/util.go b/fxhttpserver/util.go index ec291194..dfa8fcf6 100644 --- a/fxhttpserver/util.go +++ b/fxhttpserver/util.go @@ -2,6 +2,17 @@ package fxhttpserver import "strings" +// Contains returns true if a given string can be found in a given slice of strings. +func Contains(list []string, str string) bool { + for _, i := range list { + if i == str { + return true + } + } + + return false +} + // Sanitize transforms a given string to not contain spaces or dashes, and to be in lower case. func Sanitize(str string) string { san := strings.ReplaceAll(str, " ", "_")