diff --git a/.gitignore b/.gitignore index 93df6b44..bfb61df4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ uncors .idea node_modules +.uncors.yaml diff --git a/go.mod b/go.mod index 65ca2c06..e206bf88 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.19 require ( github.com/PuerkitoBio/purell v1.2.0 + github.com/bmatcuk/doublestar/v4 v4.6.0 github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a github.com/go-playground/assert/v2 v2.2.0 github.com/go-playground/validator/v10 v10.14.1 @@ -18,7 +19,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 - golang.org/x/net v0.11.0 + golang.org/x/net v0.12.0 ) require ( @@ -37,6 +38,7 @@ require ( github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect @@ -44,11 +46,11 @@ require ( github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/crypto v0.10.0 // indirect + golang.org/x/crypto v0.11.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect - golang.org/x/sys v0.9.0 // indirect - golang.org/x/term v0.9.0 // indirect - golang.org/x/text v0.10.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index f64a3818..fa31b40b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,4 @@ atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= -atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= atomicgo.dev/cursor v0.1.2 h1:zLIcqxTFymd9Uv2gloPEv5YfnnCkJ4SCdPlYm5374pA= atomicgo.dev/cursor v0.1.2/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= @@ -25,43 +24,26 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0 h1:PQcPefKFdaIzjQFbiyOgAqyx8q5djaE7x9Sqe712DPA= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.19.0 h1:+9zda3WGgW1ZSTlVppLCYFIr48Pa35q1uG2N1itbCEQ= -cloud.google.com/go/compute v1.19.0/go.mod h1:rikpw2y+UMidAe9tISo04EHNOIf42RLYF/q8Bs93scU= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA= -cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= -cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0 h1:6RRlFMv1omScs6iq2hfE3IvgE+l6RfJPampq8UZc5TU= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs= github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8= @@ -71,35 +53,22 @@ github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzX github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= -github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig= github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk= -github.com/armon/go-metrics v0.4.0 h1:yCQqn7dwca4ITXb+CbubHmedzaQYHhNhrEXLYUeEe8Q= -github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/atomicgo/cursor v0.0.1 h1:xdogsqa6YYlLfM+GyClC/Lchf7aiMerFiZQn7soTOoU= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= +github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc= +github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bradleyjkemp/cupaloy v2.3.0+incompatible h1:UafIjBvWQmS9i/xRg+CamMrnLTKNzo+bdmT/oH34c2Y= -github.com/bradleyjkemp/cupaloy v2.3.0+incompatible/go.mod h1:Au1Xw1sgaJ5iSFktEhYsS0dbQiS1B0/XMXl+42y9Ilk= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -107,22 +76,15 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= 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 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= @@ -134,24 +96,18 @@ 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.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gojuno/minimock/v3 v3.1.3 h1:9jakBeOqffZvR9BGBTulphLwiUfiju1w7JspU5eX/fY= github.com/gojuno/minimock/v3 v3.1.3/go.mod h1:WylRuaQInND/eg0HqP0/6etOdtv67AIfOgPW1z8QtKU= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -167,10 +123,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -182,11 +135,8 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -197,21 +147,11 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2 h1:LR89qFljJ48s990kEKGsk213yIJDPI4205OKOzbURK8= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE= -github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.8.0 h1:UBtEZqx1bjXtOQ5BVTkuYghXrr3N4V123VKJK67vJZc= -github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 h1:tlyzajkF3030q6M8SvmJSemC9DTHL/xaMa18b65+JM4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= @@ -219,83 +159,46 @@ github.com/gookit/color v1.5.3 h1:twfIhZs4QLCtimkP7MOxlF3A0U/5cDPseRT9M/+2SCE= github.com/gookit/color v1.5.3/go.mod h1:NUzwzeehUfl7GIb36pqId+UGmRfQcU/WiiyTTeNjHtE= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/hashicorp/consul/api v1.20.0 h1:9IHTjNVSZ7MIwjlW3N3a7iGiykCMDpxZu8jsxFJh0yc= -github.com/hashicorp/consul/api v1.20.0/go.mod h1:nR64eD44KQ59Of/ECwt2vUmIK2DKsDzAwTmwmLl8Wpo= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 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/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= -github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/hexdigest/gowrap v1.1.8 h1:xGTnuMvHou3sa+PSHphOCxPJTJyqNRvGl21t/p3eLes= -github.com/hexdigest/gowrap v1.1.8/go.mod h1:H/JiFmQMp//tedlV8qt2xBdGzmne6bpbaSuiHmygnMw= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 h1:mV02weKRL81bEnm8A0HT1/CAelMQDBuQIfLw8n+d6xI= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/pseidemann/finish v1.2.0 h1:XrEc9FCnBPulyM9NvAptAtcOCZZYHwV0MRCcnCfQlnw= github.com/pseidemann/finish v1.2.0/go.mod h1:Wl17vXLhlT9a/K7jryhExgJPfbs4+dUpRaauEWt7oQ4= @@ -313,9 +216,6 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 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/sagikazarmark/crypt v0.10.0 h1:96E1qrToLBU6fGzo+PRRz7KGOc9FkYFiPnR3/zf8Smg= -github.com/sagikazarmark/crypt v0.10.0/go.mod h1:gwTNHQVoOS3xp9Xvz5LLR+1AauC5M6880z5NWzdhOyQ= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= @@ -332,7 +232,6 @@ github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= 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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -354,30 +253,13 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/etcd/api/v3 v3.5.9 h1:4wSsluwyTbGGmyjJktOf3wFQoTBIURXHnq9n/G/JQHs= -go.etcd.io/etcd/api/v3 v3.5.9/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k= -go.etcd.io/etcd/client/pkg/v3 v3.5.9 h1:oidDC4+YEuSIQbsR94rY9gur91UPL6DnxDCIYd2IGsE= -go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4= -go.etcd.io/etcd/client/v2 v2.305.7 h1:AELPkjNR3/igjbO7CjyF1fPuVPjrblliiKj+Y6xSGOU= -go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s= -go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E= -go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -386,8 +268,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= -golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -401,7 +283,6 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -413,10 +294,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= @@ -428,8 +307,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -464,8 +341,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= -golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -475,8 +352,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -488,7 +363,6 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -533,15 +407,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= -golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= 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= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -552,13 +426,11 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= -golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -607,14 +479,11 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -634,15 +503,12 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513 google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es= -google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -680,8 +546,6 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -698,8 +562,6 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -710,19 +572,14 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -734,11 +591,7 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/config/cache.go b/internal/config/cache.go new file mode 100644 index 00000000..44fe1ba3 --- /dev/null +++ b/internal/config/cache.go @@ -0,0 +1,37 @@ +package config + +import ( + "time" +) + +type CacheGlobs []string + +func (globs CacheGlobs) Clone() CacheGlobs { + if globs == nil { + return nil + } + + cacheGlobs := make(CacheGlobs, 0, len(globs)) + cacheGlobs = append(cacheGlobs, globs...) + + return cacheGlobs +} + +type CacheConfig struct { + ExpirationTime time.Duration `mapstructure:"expiration-time"` + ClearTime time.Duration `mapstructure:"clear-time"` + Methods []string `mapstructure:"methods"` +} + +func (config *CacheConfig) Clone() *CacheConfig { + var methods []string + if config.Methods != nil { + methods = append(methods, config.Methods...) + } + + return &CacheConfig{ + ExpirationTime: config.ExpirationTime, + ClearTime: config.ClearTime, + Methods: methods, + } +} diff --git a/internal/config/cache_test.go b/internal/config/cache_test.go new file mode 100644 index 00000000..0c5059a9 --- /dev/null +++ b/internal/config/cache_test.go @@ -0,0 +1,51 @@ +package config_test + +import ( + "net/http" + "testing" + "time" + + "github.com/evg4b/uncors/internal/config" + "github.com/stretchr/testify/assert" +) + +func TestCacheGlobsClone(t *testing.T) { + globs := config.CacheGlobs{ + "/api/**", + "/constants", + "/translations", + "/**/*.js", + } + + cacheGlobs := globs.Clone() + + t.Run("not same", func(t *testing.T) { + assert.NotSame(t, globs, cacheGlobs) + }) + + t.Run("equals values", func(t *testing.T) { + assert.EqualValues(t, globs, cacheGlobs) + }) +} + +func TestCacheConfigClone(t *testing.T) { + cacheConfig := &config.CacheConfig{ + ExpirationTime: 5 * time.Minute, + ClearTime: 30 * time.Second, + Methods: []string{http.MethodGet, http.MethodPost}, + } + + clonedCacheConfig := cacheConfig.Clone() + + t.Run("not same", func(t *testing.T) { + assert.NotSame(t, cacheConfig, clonedCacheConfig) + }) + + t.Run("equals values", func(t *testing.T) { + assert.EqualValues(t, cacheConfig, clonedCacheConfig) + }) + + t.Run("not same methods", func(t *testing.T) { + assert.NotSame(t, cacheConfig.Methods, clonedCacheConfig.Methods) + }) +} diff --git a/internal/config/config.go b/internal/config/config.go index 3f191748..7d976cfe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,7 +3,6 @@ package config import ( "fmt" - "github.com/evg4b/uncors/internal/config/hooks" "github.com/mitchellh/mapstructure" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -15,13 +14,14 @@ const ( ) type UncorsConfig struct { - HTTPPort int `mapstructure:"http-port" validate:"required"` - Mappings Mappings `mapstructure:"mappings" validate:"required"` - Proxy string `mapstructure:"proxy"` - Debug bool `mapstructure:"debug"` - HTTPSPort int `mapstructure:"https-port"` - CertFile string `mapstructure:"cert-file"` - KeyFile string `mapstructure:"key-file"` + HTTPPort int `mapstructure:"http-port" validate:"required"` + Mappings Mappings `mapstructure:"mappings" validate:"required"` + Proxy string `mapstructure:"proxy"` + Debug bool `mapstructure:"debug"` + HTTPSPort int `mapstructure:"https-port"` + CertFile string `mapstructure:"cert-file"` + KeyFile string `mapstructure:"key-file"` + CacheConfig CacheConfig `mapstructure:"cache-config"` } func (config *UncorsConfig) IsHTTPSEnabled() bool { @@ -51,10 +51,11 @@ func LoadConfiguration(viperInstance *viper.Viper, args []string) (*UncorsConfig configOption := viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( mapstructure.StringToSliceHookFunc(","), - hooks.StringToTimeDurationHookFunc(), + StringToTimeDurationHookFunc(), URLMappingHookFunc(), )) + setDefaultValues(viperInstance) if err := viperInstance.Unmarshal(configuration, configOption); err != nil { return nil, fmt.Errorf("filed parsing config: %w", err) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index ec2c3cbf..a82ba57d 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,7 +1,9 @@ package config_test import ( + "net/http" "testing" + "time" "github.com/evg4b/uncors/internal/config" "github.com/evg4b/uncors/testing/testconstants" @@ -47,6 +49,12 @@ debug: true https-port: 8081 cert-file: /etc/certificates/cert-file.pem key-file: /etc/certificates/key-file.key +cache-config: + expiration-time: 1h + clear-time: 30m + methods: + - GET + - POST ` ) @@ -89,6 +97,11 @@ func TestLoadConfiguration(t *testing.T) { HTTPPort: 80, HTTPSPort: 443, Mappings: config.Mappings{}, + CacheConfig: config.CacheConfig{ + ExpirationTime: config.DefaultExpirationTime, + ClearTime: config.DefaultClearTime, + Methods: []string{http.MethodGet}, + }, }, }, { @@ -100,6 +113,11 @@ func TestLoadConfiguration(t *testing.T) { Mappings: config.Mappings{ {From: testconstants.HTTPLocalhost, To: testconstants.HTTPSGithub}, }, + CacheConfig: config.CacheConfig{ + ExpirationTime: config.DefaultExpirationTime, + ClearTime: config.DefaultClearTime, + Methods: []string{http.MethodGet}, + }, }, }, { @@ -139,6 +157,14 @@ func TestLoadConfiguration(t *testing.T) { HTTPSPort: 8081, CertFile: testconstants.CertFilePath, KeyFile: testconstants.KeyFilePath, + CacheConfig: config.CacheConfig{ + ExpirationTime: time.Hour, + ClearTime: 30 * time.Minute, + Methods: []string{ + http.MethodGet, + http.MethodPost, + }, + }, }, }, { @@ -186,6 +212,14 @@ func TestLoadConfiguration(t *testing.T) { HTTPSPort: 8081, CertFile: testconstants.CertFilePath, KeyFile: testconstants.KeyFilePath, + CacheConfig: config.CacheConfig{ + ExpirationTime: time.Hour, + ClearTime: 30 * time.Minute, + Methods: []string{ + http.MethodGet, + http.MethodPost, + }, + }, }, }, } diff --git a/internal/config/default.go b/internal/config/default.go new file mode 100644 index 00000000..04efa8b5 --- /dev/null +++ b/internal/config/default.go @@ -0,0 +1,19 @@ +package config + +import ( + "net/http" + "time" + + "github.com/spf13/viper" +) + +const ( + DefaultExpirationTime = 30 * time.Minute + DefaultClearTime = 30 * time.Minute +) + +func setDefaultValues(instance *viper.Viper) { + instance.SetDefault("cache-config.expiration-time", DefaultExpirationTime) + instance.SetDefault("cache-config.clear-time", DefaultClearTime) + instance.SetDefault("cache-config.methods", []string{http.MethodGet}) +} diff --git a/internal/config/helpers.go b/internal/config/helpers.go index 858163c9..18852cb4 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -8,7 +8,6 @@ import ( "strconv" "strings" - "github.com/evg4b/uncors/internal/config/hooks" "github.com/evg4b/uncors/pkg/urlx" "github.com/mitchellh/mapstructure" "github.com/samber/lo" @@ -51,7 +50,7 @@ func readURLMapping(config *viper.Viper, configuration *UncorsConfig) error { func decodeConfig[T any](data any, mapping *T, decodeFuncs ...mapstructure.DecodeHookFunc) error { hook := mapstructure.ComposeDecodeHookFunc( - hooks.StringToTimeDurationHookFunc(), + StringToTimeDurationHookFunc(), mapstructure.StringToSliceHookFunc(","), mapstructure.ComposeDecodeHookFunc(decodeFuncs...), ) diff --git a/internal/config/mapping.go b/internal/config/mapping.go index dd51394e..dda140e5 100644 --- a/internal/config/mapping.go +++ b/internal/config/mapping.go @@ -12,6 +12,7 @@ type Mapping struct { To string `mapstructure:"to"` Statics StaticDirectories `mapstructure:"statics"` Mocks Mocks `mapstructure:"mocks"` + Cache CacheGlobs `mapstructure:"cache"` } func (u *Mapping) Clone() Mapping { @@ -20,6 +21,7 @@ func (u *Mapping) Clone() Mapping { To: u.To, Statics: u.Statics.Clone(), Mocks: u.Mocks.Clone(), + Cache: u.Cache.Clone(), } } diff --git a/internal/config/mappings.go b/internal/config/mappings.go index 939b3114..b26fe5a9 100644 --- a/internal/config/mappings.go +++ b/internal/config/mappings.go @@ -25,6 +25,9 @@ func (mappings Mappings) String() string { for _, static := range mapping.Statics { builder.WriteString(sfmt.Sprintf(" static: %s => %s\n", static.Path, static.Dir)) } + for _, cacheGlob := range mapping.Cache { + builder.WriteString(sfmt.Sprintf(" cache: %s\n", cacheGlob)) + } } builder.WriteString("\n") diff --git a/internal/config/model.go b/internal/config/mock.go similarity index 61% rename from internal/config/model.go rename to internal/config/mock.go index 136991bb..f07bbad6 100644 --- a/internal/config/model.go +++ b/internal/config/mock.go @@ -1,30 +1,10 @@ package config import ( - "time" - "github.com/evg4b/uncors/internal/helpers" "github.com/samber/lo" ) -type Response struct { - Code int `mapstructure:"code"` - Headers map[string]string `mapstructure:"headers"` - Raw string `mapstructure:"raw"` - File string `mapstructure:"file"` - Delay time.Duration `mapstructure:"delay"` -} - -func (r *Response) Clone() Response { - return Response{ - Code: r.Code, - Headers: helpers.CloneMap(r.Headers), - Raw: r.Raw, - File: r.File, - Delay: r.Delay, - } -} - type Mock struct { Path string `mapstructure:"path"` Method string `mapstructure:"method"` diff --git a/internal/config/model_test.go b/internal/config/mock_test.go similarity index 68% rename from internal/config/model_test.go rename to internal/config/mock_test.go index 699db26b..cfc32263 100644 --- a/internal/config/model_test.go +++ b/internal/config/mock_test.go @@ -3,40 +3,12 @@ package config_test import ( "net/http" "testing" - "time" "github.com/evg4b/uncors/internal/config" "github.com/go-http-utils/headers" "github.com/stretchr/testify/assert" ) -func TestResponseClone(t *testing.T) { - response := config.Response{ - Code: http.StatusOK, - Headers: map[string]string{ - headers.ContentType: "plain/text", - headers.CacheControl: "none", - }, - Raw: "this is plain text", - File: "~/projects/uncors/response/demo.json", - Delay: time.Hour, - } - - clonedResponse := response.Clone() - - t.Run("not same", func(t *testing.T) { - assert.NotSame(t, &response, &clonedResponse) - }) - - t.Run("equals values", func(t *testing.T) { - assert.EqualValues(t, response, clonedResponse) - }) - - t.Run("not same Headers map", func(t *testing.T) { - assert.NotSame(t, &response.Headers, &clonedResponse.Headers) - }) -} - func TestMockClone(t *testing.T) { mock := config.Mock{ Path: "/constants", diff --git a/internal/config/response.go b/internal/config/response.go new file mode 100644 index 00000000..d7c49159 --- /dev/null +++ b/internal/config/response.go @@ -0,0 +1,25 @@ +package config + +import ( + "time" + + "github.com/evg4b/uncors/internal/helpers" +) + +type Response struct { + Code int `mapstructure:"code"` + Headers map[string]string `mapstructure:"headers"` + Raw string `mapstructure:"raw"` + File string `mapstructure:"file"` + Delay time.Duration `mapstructure:"delay"` +} + +func (r *Response) Clone() Response { + return Response{ + Code: r.Code, + Headers: helpers.CloneMap(r.Headers), + Raw: r.Raw, + File: r.File, + Delay: r.Delay, + } +} diff --git a/internal/config/response_test.go b/internal/config/response_test.go new file mode 100644 index 00000000..487df1fa --- /dev/null +++ b/internal/config/response_test.go @@ -0,0 +1,38 @@ +package config_test + +import ( + "net/http" + "testing" + "time" + + "github.com/evg4b/uncors/internal/config" + "github.com/go-http-utils/headers" + "github.com/stretchr/testify/assert" +) + +func TestResponseClone(t *testing.T) { + response := config.Response{ + Code: http.StatusOK, + Headers: map[string]string{ + headers.ContentType: "plain/text", + headers.CacheControl: "none", + }, + Raw: "this is plain text", + File: "~/projects/uncors/response/demo.json", + Delay: time.Hour, + } + + clonedResponse := response.Clone() + + t.Run("not same", func(t *testing.T) { + assert.NotSame(t, &response, &clonedResponse) + }) + + t.Run("equals values", func(t *testing.T) { + assert.EqualValues(t, response, clonedResponse) + }) + + t.Run("not same Headers map", func(t *testing.T) { + assert.NotSame(t, &response.Headers, &clonedResponse.Headers) + }) +} diff --git a/internal/config/hooks/time_decode_hook.go b/internal/config/time_decode_hook.go similarity index 96% rename from internal/config/hooks/time_decode_hook.go rename to internal/config/time_decode_hook.go index 1968e084..c3d3367f 100644 --- a/internal/config/hooks/time_decode_hook.go +++ b/internal/config/time_decode_hook.go @@ -1,4 +1,4 @@ -package hooks +package config import ( "reflect" diff --git a/internal/config/hooks/time_decode_hook_test.go b/internal/config/time_decode_hook_test.go similarity index 96% rename from internal/config/hooks/time_decode_hook_test.go rename to internal/config/time_decode_hook_test.go index 0d36e656..5af95964 100644 --- a/internal/config/hooks/time_decode_hook_test.go +++ b/internal/config/time_decode_hook_test.go @@ -1,10 +1,10 @@ -package hooks_test +package config_test import ( "testing" "time" - "github.com/evg4b/uncors/internal/config/hooks" + "github.com/evg4b/uncors/internal/config" "github.com/evg4b/uncors/testing/testutils" "github.com/mitchellh/mapstructure" "github.com/spf13/viper" @@ -15,7 +15,7 @@ func TestStringToTimeDurationHookFunc(t *testing.T) { const key = "duration" viperInstance := viper.New() configOption := viper.DecodeHook(mapstructure.ComposeDecodeHookFunc( - hooks.StringToTimeDurationHookFunc(), + config.StringToTimeDurationHookFunc(), mapstructure.OrComposeDecodeHookFunc( mapstructure.StringToSliceHookFunc(","), mapstructure.StringToSliceHookFunc(", "), diff --git a/internal/contracts/handler.go b/internal/contracts/handler.go deleted file mode 100644 index 061c26bf..00000000 --- a/internal/contracts/handler.go +++ /dev/null @@ -1,11 +0,0 @@ -package contracts - -import ( - "net/url" - - "github.com/evg4b/uncors/internal/urlreplacer" -) - -type URLReplacerFactory interface { - Make(requestURL *url.URL) (*urlreplacer.Replacer, *urlreplacer.Replacer, error) -} diff --git a/internal/contracts/http.go b/internal/contracts/http.go new file mode 100644 index 00000000..84e9e985 --- /dev/null +++ b/internal/contracts/http.go @@ -0,0 +1,35 @@ +package contracts + +import ( + "errors" + "net/http" +) + +type Request = http.Request + +type Handler interface { + ServeHTTP(ResponseWriter, *Request) +} + +type MiddlewareHandler interface { + Wrap(next Handler) Handler +} + +type HandlerFunc func(ResponseWriter, *Request) + +func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) +} + +var ErrResponseNotCasted = errors.New("received incorrect response writer type") + +func CastToHTTPHandler(handler Handler) http.Handler { + return http.HandlerFunc(func(response http.ResponseWriter, request *http.Request) { + writer, ok := response.(ResponseWriter) + if !ok { + panic(ErrResponseNotCasted) + } + + handler.ServeHTTP(writer, request) + }) +} diff --git a/internal/contracts/http_test.go b/internal/contracts/http_test.go new file mode 100644 index 00000000..b7d00505 --- /dev/null +++ b/internal/contracts/http_test.go @@ -0,0 +1,57 @@ +package contracts_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/evg4b/uncors/internal/contracts" + "github.com/evg4b/uncors/internal/sfmt" + "github.com/evg4b/uncors/testing/testutils" + "github.com/stretchr/testify/assert" +) + +func TestCastToHTTPHandler(t *testing.T) { + const expectedBody = `{ "OK": true }` + uncorsHandler := contracts.HandlerFunc(func(w contracts.ResponseWriter, r *contracts.Request) { + w.WriteHeader(http.StatusOK) + sfmt.Fprint(w, expectedBody) + }) + + request := httptest.NewRequest(http.MethodGet, "/data", nil) + httpHandler := contracts.CastToHTTPHandler(uncorsHandler) + + t.Run("cast correctly", func(t *testing.T) { + recorder := httptest.NewRecorder() + responseWriter := contracts.WrapResponseWriter(recorder) + + assert.NotPanics(t, func() { + httpHandler.ServeHTTP(responseWriter, request) + assert.Equal(t, expectedBody, testutils.ReadBody(t, recorder)) + }) + }) + + t.Run("panic when request is not wrapped", func(t *testing.T) { + recorder := httptest.NewRecorder() + + assert.PanicsWithValue(t, contracts.ErrResponseNotCasted, func() { + httpHandler.ServeHTTP(recorder, request) + }) + }) +} + +func TestHandlerFunc(t *testing.T) { + const expectedBody = `{ "OK": true }` + uncorsHandler := contracts.HandlerFunc(func(w contracts.ResponseWriter, r *contracts.Request) { + w.WriteHeader(http.StatusOK) + sfmt.Fprint(w, expectedBody) + }) + + recorder := httptest.NewRecorder() + responseWriter := contracts.WrapResponseWriter(recorder) + request := httptest.NewRequest(http.MethodGet, "/data", nil) + + uncorsHandler.ServeHTTP(responseWriter, request) + + assert.Equal(t, expectedBody, testutils.ReadBody(t, recorder)) +} diff --git a/internal/contracts/logger.go b/internal/contracts/logger.go index 4092040b..d2924030 100644 --- a/internal/contracts/logger.go +++ b/internal/contracts/logger.go @@ -1,9 +1,5 @@ package contracts -import ( - "net/http" -) - type Logger interface { Error(a ...any) Errorf(template string, a ...any) @@ -13,5 +9,5 @@ type Logger interface { Infof(template string, a ...any) Debug(a ...any) Debugf(template string, a ...any) - PrintResponse(response *http.Response) + PrintResponse(request *Request, code int) } diff --git a/internal/contracts/response_writer.go b/internal/contracts/response_writer.go new file mode 100644 index 00000000..f80a6fcf --- /dev/null +++ b/internal/contracts/response_writer.go @@ -0,0 +1,26 @@ +package contracts + +import "net/http" + +type ResponseWriter interface { + http.ResponseWriter + StatusCode() int +} + +type ResponseWriterWrap struct { + Code int + http.ResponseWriter +} + +func WrapResponseWriter(writer http.ResponseWriter) *ResponseWriterWrap { + return &ResponseWriterWrap{ResponseWriter: writer} +} + +func (r *ResponseWriterWrap) WriteHeader(statusCode int) { + r.Code = statusCode + r.ResponseWriter.WriteHeader(statusCode) +} + +func (r *ResponseWriterWrap) StatusCode() int { + return r.Code +} diff --git a/internal/handler/static/response_writer_test.go b/internal/contracts/response_writer_test.go similarity index 76% rename from internal/handler/static/response_writer_test.go rename to internal/contracts/response_writer_test.go index 5ae5bdbc..db9c5c4d 100644 --- a/internal/handler/static/response_writer_test.go +++ b/internal/contracts/response_writer_test.go @@ -1,10 +1,10 @@ -package static_test +package contracts_test import ( "net/http/httptest" "testing" - "github.com/evg4b/uncors/internal/handler/static" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/sfmt" "github.com/evg4b/uncors/testing/testutils" "github.com/stretchr/testify/assert" @@ -15,13 +15,13 @@ func TestResponseWriterWrapper(t *testing.T) { const expectedCode = 201 recorder := httptest.NewRecorder() - writer := static.WrapResponseWriter(recorder) + writer := contracts.WrapResponseWriter(recorder) writer.WriteHeader(expectedCode) sfmt.Fprint(writer, expectedValue) t.Run("save status code", func(t *testing.T) { - assert.Equal(t, expectedCode, writer.StatusCode) + assert.Equal(t, expectedCode, writer.StatusCode()) }) t.Run("write body", func(t *testing.T) { diff --git a/internal/handler/cache/cacheable_writer.go b/internal/handler/cache/cacheable_writer.go new file mode 100644 index 00000000..c8f4d757 --- /dev/null +++ b/internal/handler/cache/cacheable_writer.go @@ -0,0 +1,64 @@ +package cache + +import ( + "bytes" + "io" + "net/http" + + "github.com/go-http-utils/headers" +) + +type CachedResponse struct { + Code int + Body []byte + Header http.Header +} + +type CacheableResponseWriter struct { + original http.ResponseWriter + outputWriter io.Writer + buffer *bytes.Buffer + code int +} + +func NewCacheableWriter(original http.ResponseWriter) *CacheableResponseWriter { + buffer := &bytes.Buffer{} + + return &CacheableResponseWriter{ + original: original, + outputWriter: io.MultiWriter(buffer, original), + buffer: buffer, + } +} + +func (w *CacheableResponseWriter) Header() http.Header { + return w.original.Header() +} + +func (w *CacheableResponseWriter) Write(bytes []byte) (int, error) { + return w.outputWriter.Write(bytes) //nolint: wrapcheck +} + +func (w *CacheableResponseWriter) StatusCode() int { + return w.code +} + +func (w *CacheableResponseWriter) WriteHeader(statusCode int) { + w.code = statusCode + w.original.WriteHeader(statusCode) +} + +func (w *CacheableResponseWriter) GetCachedResponse() *CachedResponse { + header := w.original.Header().Clone() + cleanupHeader(header) + + return &CachedResponse{ + Code: w.code, + Body: w.buffer.Bytes(), + Header: header, + } +} + +func cleanupHeader(header http.Header) { + header.Del(headers.ContentLength) +} diff --git a/internal/handler/cache/cacheable_writer_test.go b/internal/handler/cache/cacheable_writer_test.go new file mode 100644 index 00000000..a0be5c7a --- /dev/null +++ b/internal/handler/cache/cacheable_writer_test.go @@ -0,0 +1,107 @@ +package cache_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/evg4b/uncors/internal/handler/cache" + "github.com/evg4b/uncors/internal/sfmt" + "github.com/go-http-utils/headers" + "github.com/stretchr/testify/assert" +) + +func TestCacheableResponseWriter(t *testing.T) { + const defaultContentType = "text/plain; charset=utf-8" + const customContentType = "application/xml" + const authorization = "xxxxxx" + const bodyString = "Test Body" + bodyBytes := []byte{0x54, 0x65, 0x73, 0x74, 0x20, 0x42, 0x6f, 0x64, 0x79} + + tests := []struct { + name string + action func(w http.ResponseWriter) + expected *cache.CachedResponse + }{ + { + name: "write body bytes only", + action: func(w http.ResponseWriter) { + sfmt.Fprint(w, bodyString) + }, + expected: &cache.CachedResponse{ + Header: http.Header{ + headers.ContentType: {defaultContentType}, + }, + Body: bodyBytes, + }, + }, + { + name: "write status code only", + action: func(w http.ResponseWriter) { + w.WriteHeader(http.StatusBadGateway) + }, + expected: &cache.CachedResponse{ + Header: http.Header{}, + Code: http.StatusBadGateway, + }, + }, + { + name: "write headers only", + action: func(w http.ResponseWriter) { + header := w.Header() + header.Set(headers.ContentType, customContentType) + header.Set(headers.Authorization, authorization) + }, + expected: &cache.CachedResponse{ + Header: http.Header{ + headers.ContentType: {customContentType}, + headers.Authorization: {authorization}, + }, + }, + }, + { + name: "remove unsaved headers", + action: func(w http.ResponseWriter) { + header := w.Header() + header.Set(headers.ContentLength, "999") + sfmt.Fprint(w, bodyString) + }, + expected: &cache.CachedResponse{ + Header: http.Header{ + headers.ContentType: {defaultContentType}, + }, + Body: bodyBytes, + }, + }, + { + name: "write full request", + action: func(writer http.ResponseWriter) { + header := writer.Header() + header.Set(headers.ContentType, customContentType) + header.Set(headers.ContentLength, "9") + header.Set(headers.Authorization, authorization) + writer.WriteHeader(http.StatusBadGateway) + sfmt.Fprint(writer, bodyString) + }, + expected: &cache.CachedResponse{ + Code: http.StatusBadGateway, + Header: http.Header{ + headers.ContentType: {customContentType}, + headers.Authorization: {authorization}, + }, + Body: bodyBytes, + }, + }, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + cacheableWriter := cache.NewCacheableWriter(recorder) + + testCase.action(cacheableWriter) + actual := cacheableWriter.GetCachedResponse() + + assert.Equal(t, testCase.expected, actual) + }) + } +} diff --git a/internal/handler/cache/middleware.go b/internal/handler/cache/middleware.go new file mode 100644 index 00000000..f2f56d53 --- /dev/null +++ b/internal/handler/cache/middleware.go @@ -0,0 +1,114 @@ +package cache + +import ( + "net/url" + "sort" + "strings" + + "github.com/bmatcuk/doublestar/v4" + "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" + "github.com/evg4b/uncors/internal/helpers" + "github.com/patrickmn/go-cache" + "github.com/samber/lo" +) + +type Middleware struct { + logger contracts.Logger + storage *cache.Cache + prefix string + methods []string + pathGlobs config.CacheGlobs +} + +func NewMiddleware(options ...MiddlewareOption) *Middleware { + middleware := &Middleware{} + + for _, option := range options { + option(middleware) + } + + helpers.AssertIsDefined(middleware.logger, "Logger is not configured") + helpers.AssertIsDefined(middleware.storage, "Cache storage is not configured") + + return middleware +} + +func (m *Middleware) Wrap(next contracts.Handler) contracts.Handler { + return contracts.HandlerFunc(func(writer contracts.ResponseWriter, request *contracts.Request) { + if !m.isCacheableRequest(request) { + next.ServeHTTP(writer, request) + + return + } + + m.cacheRequest(writer, request, next) + }) +} + +func (m *Middleware) cacheRequest(writer contracts.ResponseWriter, request *contracts.Request, next contracts.Handler) { + cacheKey := m.extractCacheKey(request.URL) + if cachedResponse := m.getCachedResponse(cacheKey); cachedResponse != nil { + m.writeCachedResponse(writer, cachedResponse) + + m.logger.PrintResponse(request, writer.StatusCode()) + + return + } + + cacheableWriter := NewCacheableWriter(writer) + next.ServeHTTP(cacheableWriter, request) + if helpers.Is2xxCode(cacheableWriter.StatusCode()) { + response := cacheableWriter.GetCachedResponse() + m.storage.SetDefault(cacheKey, response) + } +} + +func (m *Middleware) writeCachedResponse(writer contracts.ResponseWriter, cachedResponse *CachedResponse) { + header := writer.Header() + for key, values := range cachedResponse.Header { + for _, value := range values { + header.Add(key, value) + } + } + + writer.WriteHeader(cachedResponse.Code) + if cachedResponse.Body != nil && len(cachedResponse.Body) > 0 { + if _, err := writer.Write(cachedResponse.Body); err != nil { + panic(err) + } + } +} + +func (m *Middleware) isCacheableRequest(request *contracts.Request) bool { + return lo.Contains(m.methods, request.Method) && lo.ContainsBy(m.pathGlobs, func(pattern string) bool { + ok, err := doublestar.PathMatch(pattern, request.URL.Path) + if err != nil { + panic(err) + } + + return ok + }) +} + +func (m *Middleware) extractCacheKey(url *url.URL) string { + values := url.Query() + items := make([]string, 0, len(values)) + for key, value := range values { + sort.Strings(value) + valuesKey := key + "=" + strings.Join(value, ",") + items = append(items, valuesKey) + } + + sort.Strings(items) + + return m.prefix + url.Path + "?" + strings.Join(items, ";") +} + +func (m *Middleware) getCachedResponse(cacheKey string) *CachedResponse { + if cachedResponse, ok := m.storage.Get(cacheKey); ok { + return cachedResponse.(*CachedResponse) // nolint: forcetypeassert + } + + return nil +} diff --git a/internal/handler/cache/middleware_test.go b/internal/handler/cache/middleware_test.go new file mode 100644 index 00000000..9514f07d --- /dev/null +++ b/internal/handler/cache/middleware_test.go @@ -0,0 +1,175 @@ +package cache_test + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" + "github.com/evg4b/uncors/internal/handler/cache" + "github.com/evg4b/uncors/internal/sfmt" + "github.com/evg4b/uncors/testing/mocks" + "github.com/evg4b/uncors/testing/testutils" + "github.com/go-http-utils/headers" + goCache "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/assert" +) + +func TestCacheMiddleware(t *testing.T) { + const expectedBody = "this is test" + + expectedHeader := http.Header{ + headers.ContentType: {"text/html; charset=iso-8859-1"}, + headers.ContentEncoding: {"deflate, gzip"}, + } + + middleware := cache.NewMiddleware( + cache.WithCacheStorage(goCache.New(time.Minute, time.Minute)), + cache.WithLogger(mocks.NewNoopLogger(t)), + cache.WithMethods([]string{http.MethodGet}), + cache.WithGlobs(config.CacheGlobs{ + "/translations", + "/api/**", + }), + ) + + t.Run("should not call cached response just one time for", func(t *testing.T) { + tests := []struct { + name string + method string + path string + statusCode int + }{ + { + name: "request in glob", + method: http.MethodGet, + path: "/api", + statusCode: http.StatusOK, + }, + { + name: "request in glob with params", + method: http.MethodGet, + path: "/api?some=params", + statusCode: http.StatusOK, + }, + { + name: "request in glob with other params", + method: http.MethodGet, + path: "/api?other=params", + statusCode: http.StatusOK, + }, + { + name: "second level request in glob", + method: http.MethodGet, + path: "/api/comments", + statusCode: http.StatusOK, + }, + { + name: "second level request in glob with params", + method: http.MethodGet, + path: "/api/comments?q=test", + statusCode: http.StatusOK, + }, + { + name: "third level request in glob", + method: http.MethodGet, + path: "/api/comments/1", + statusCode: http.StatusOK, + }, + { + name: "third level request in glob with params", + method: http.MethodGet, + path: "/api/comments/1?q=demo", + statusCode: http.StatusOK, + }, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + handler := testutils.NewCounter(func(writer contracts.ResponseWriter, request *contracts.Request) { + writer.WriteHeader(testCase.statusCode) + testutils.CopyHeaders(expectedHeader, writer.Header()) + sfmt.Fprintf(writer, expectedBody) + }) + + wrappedHandler := middleware.Wrap(handler) + + testutils.Times(5, func() { + recorder := httptest.NewRecorder() + wrappedHandler.ServeHTTP( + contracts.WrapResponseWriter(recorder), + httptest.NewRequest(testCase.method, testCase.path, nil), + ) + assert.Equal(t, expectedHeader, recorder.Header()) + assert.Equal(t, expectedBody, testutils.ReadBody(t, recorder)) + }) + + assert.Equal(t, 1, handler.Count()) + }) + } + }) + + t.Run("should not cache response", func(t *testing.T) { + tests := []struct { + name string + method string + path string + statusCode int + }{ + { + name: "witch path out of glob", + method: http.MethodGet, + path: "/test", + statusCode: http.StatusOK, + }, + { + name: "from POST method request", + method: http.MethodPost, + path: "/api", + statusCode: http.StatusOK, + }, + { + name: "witch response with status code 500", + method: http.MethodGet, + path: "/api/constants", + statusCode: http.StatusInternalServerError, + }, + { + name: "witch response with status code 400", + method: http.MethodGet, + path: "/api/constants", + statusCode: http.StatusBadRequest, + }, + { + name: "witch response with status code 304", + method: http.MethodGet, + path: "/api/constants", + statusCode: http.StatusNotModified, + }, + } + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + handler := testutils.NewCounter(func(writer contracts.ResponseWriter, request *contracts.Request) { + writer.WriteHeader(testCase.statusCode) + testutils.CopyHeaders(expectedHeader, writer.Header()) + sfmt.Fprintf(writer, expectedBody) + }) + + wrappedHandler := middleware.Wrap(handler) + + testutils.Times(5, func() { + recorder := httptest.NewRecorder() + wrappedHandler.ServeHTTP( + contracts.WrapResponseWriter(recorder), + httptest.NewRequest(testCase.method, testCase.path, nil), + ) + assert.Equal(t, expectedHeader, recorder.Header()) + assert.Equal(t, expectedBody, testutils.ReadBody(t, recorder)) + }) + + assert.Equal(t, 5, handler.Count()) + }) + } + }) +} diff --git a/internal/handler/cache/options.go b/internal/handler/cache/options.go new file mode 100644 index 00000000..eda5f721 --- /dev/null +++ b/internal/handler/cache/options.go @@ -0,0 +1,39 @@ +package cache + +import ( + "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" + "github.com/patrickmn/go-cache" +) + +type MiddlewareOption = func(*Middleware) + +func WithLogger(logger contracts.Logger) MiddlewareOption { + return func(m *Middleware) { + m.logger = logger + } +} + +func WithPrefix(prefix string) MiddlewareOption { + return func(m *Middleware) { + m.prefix = prefix + } +} + +func WithMethods(methods []string) MiddlewareOption { + return func(m *Middleware) { + m.methods = methods + } +} + +func WithGlobs(globs config.CacheGlobs) MiddlewareOption { + return func(m *Middleware) { + m.pathGlobs = globs + } +} + +func WithCacheStorage(storage *cache.Cache) MiddlewareOption { + return func(m *Middleware) { + m.storage = storage + } +} diff --git a/internal/handler/mock/middleware.go b/internal/handler/mock/handler.go similarity index 58% rename from internal/handler/mock/middleware.go rename to internal/handler/mock/handler.go index fdd909b4..5d2726b3 100644 --- a/internal/handler/mock/middleware.go +++ b/internal/handler/mock/handler.go @@ -10,42 +10,41 @@ import ( "github.com/spf13/afero" ) -type Middleware struct { +type Handler struct { response config.Response logger contracts.Logger fs afero.Fs after func(duration time.Duration) <-chan time.Time } -func NewMockMiddleware(options ...MiddlewareOption) *Middleware { - middleware := &Middleware{} +func NewMockHandler(options ...HandlerOption) *Handler { + handler := &Handler{} for _, option := range options { - option(middleware) + option(handler) } - return middleware + return handler } -func (m *Middleware) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - response := m.response +func (h *Handler) ServeHTTP(writer contracts.ResponseWriter, request *contracts.Request) { + response := h.response header := writer.Header() if response.Delay > 0 { - m.logger.Debugf("Delay %s for %s", response.Delay, request.URL.RequestURI()) + h.logger.Debugf("Delay %s for %s", response.Delay, request.URL.RequestURI()) ctx := request.Context() - url := request.URL.RequestURI() waitingLoop: for { select { case <-ctx.Done(): writer.WriteHeader(http.StatusServiceUnavailable) - m.logger.Debugf("Delay is canceled (url: %s)", url) + h.logger.Debugf("Delay is canceled (url: %s)", url) return - case <-m.after(response.Delay): - m.logger.Debugf("Delay is complete (url: %s)", url) + case <-h.after(response.Delay): + h.logger.Debugf("Delay is complete (url: %s)", url) break waitingLoop } @@ -57,21 +56,18 @@ func (m *Middleware) ServeHTTP(writer http.ResponseWriter, request *http.Request header.Set(key, value) } - if len(m.response.File) > 0 { - err := m.serveFileContent(writer, request) + if len(h.response.File) > 0 { + err := h.serveFileContent(writer, request) if err != nil { infra.HTTPError(writer, err) return } } else { - m.serveRawContent(writer) + h.serveRawContent(writer) } - m.logger.PrintResponse(&http.Response{ - Request: request, - StatusCode: response.Code, - }) + h.logger.PrintResponse(request, writer.StatusCode()) } func normaliseCode(code int) int { diff --git a/internal/handler/mock/handler_options.go b/internal/handler/mock/handler_options.go new file mode 100644 index 00000000..61e71f57 --- /dev/null +++ b/internal/handler/mock/handler_options.go @@ -0,0 +1,35 @@ +package mock + +import ( + "time" + + "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" + "github.com/spf13/afero" +) + +type HandlerOption = func(*Handler) + +func WithLogger(logger contracts.Logger) HandlerOption { + return func(h *Handler) { + h.logger = logger + } +} + +func WithResponse(response config.Response) HandlerOption { + return func(h *Handler) { + h.response = response + } +} + +func WithFileSystem(fs afero.Fs) HandlerOption { + return func(h *Handler) { + h.fs = fs + } +} + +func WithAfter(after func(duration time.Duration) <-chan time.Time) HandlerOption { + return func(h *Handler) { + h.after = after + } +} diff --git a/internal/handler/mock/middleware_test.go b/internal/handler/mock/handler_test.go similarity index 93% rename from internal/handler/mock/middleware_test.go rename to internal/handler/mock/handler_test.go index 59a1c433..1c2f134b 100644 --- a/internal/handler/mock/middleware_test.go +++ b/internal/handler/mock/handler_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/handler/mock" "github.com/evg4b/uncors/testing/mocks" "github.com/evg4b/uncors/testing/testconstants" @@ -63,7 +64,7 @@ func TestHandler(t *testing.T) { } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - handler := mock.NewMockMiddleware( + handler := mock.NewMockHandler( mock.WithLogger(mocks.NewNoopLogger(t)), mock.WithResponse(testCase.response), mock.WithFileSystem(fileSystem), @@ -74,7 +75,7 @@ func TestHandler(t *testing.T) { recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/", nil) - handler.ServeHTTP(recorder, request) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) body := testutils.ReadBody(t, recorder) assert.EqualValues(t, testCase.expected, body) @@ -131,7 +132,7 @@ func TestHandler(t *testing.T) { } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - handler := mock.NewMockMiddleware( + handler := mock.NewMockHandler( mock.WithLogger(mocks.NewNoopLogger(t)), mock.WithResponse(testCase.response), mock.WithFileSystem(fileSystem), @@ -142,7 +143,7 @@ func TestHandler(t *testing.T) { recorder := httptest.NewRecorder() request := httptest.NewRequest(http.MethodGet, "/", nil) - handler.ServeHTTP(recorder, request) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) header := testutils.ReadHeader(t, recorder) assert.EqualValues(t, testCase.expected, header.Get(headers.ContentType)) @@ -220,7 +221,7 @@ func TestHandler(t *testing.T) { } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - handler := mock.NewMockMiddleware( + handler := mock.NewMockHandler( mock.WithLogger(mocks.NewNoopLogger(t)), mock.WithResponse(testCase.response), mock.WithFileSystem(fileSystem), @@ -232,7 +233,7 @@ func TestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/", nil) recorder := httptest.NewRecorder() - handler.ServeHTTP(recorder, request) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.EqualValues(t, testCase.expected, testutils.ReadHeader(t, recorder)) assert.Equal(t, http.StatusOK, recorder.Code) @@ -268,7 +269,7 @@ func TestHandler(t *testing.T) { } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - handler := mock.NewMockMiddleware( + handler := mock.NewMockHandler( mock.WithLogger(mocks.NewNoopLogger(t)), mock.WithResponse(testCase.response), mock.WithFileSystem(fileSystem), @@ -280,7 +281,7 @@ func TestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/", nil) recorder := httptest.NewRecorder() - handler.ServeHTTP(recorder, request) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, testCase.expected, recorder.Code) }) @@ -340,7 +341,7 @@ func TestHandler(t *testing.T) { for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { called := false - handler := mock.NewMockMiddleware( + handler := mock.NewMockHandler( mock.WithLogger(mocks.NewNoopLogger(t)), mock.WithResponse(testCase.response), mock.WithFileSystem(fileSystem), @@ -355,7 +356,7 @@ func TestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "/", nil) recorder := httptest.NewRecorder() - handler.ServeHTTP(recorder, request) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, called, testCase.shouldBeCalled) }) @@ -363,7 +364,7 @@ func TestHandler(t *testing.T) { }) t.Run("correctly cancel delay", func(t *testing.T) { - handler := mock.NewMockMiddleware( + handler := mock.NewMockHandler( mock.WithLogger(mocks.NewNoopLogger(t)), mock.WithResponse(config.Response{ Code: http.StatusOK, @@ -382,7 +383,7 @@ func TestHandler(t *testing.T) { waitGroup.Add(1) go func() { defer waitGroup.Done() - handler.ServeHTTP(recorder, request.WithContext(ctx)) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), request.WithContext(ctx)) }() cancel() diff --git a/internal/handler/mock/middleware_options.go b/internal/handler/mock/middleware_options.go deleted file mode 100644 index 688e2328..00000000 --- a/internal/handler/mock/middleware_options.go +++ /dev/null @@ -1,35 +0,0 @@ -package mock - -import ( - "time" - - "github.com/evg4b/uncors/internal/config" - "github.com/evg4b/uncors/internal/contracts" - "github.com/spf13/afero" -) - -type MiddlewareOption = func(*Middleware) - -func WithLogger(logger contracts.Logger) MiddlewareOption { - return func(m *Middleware) { - m.logger = logger - } -} - -func WithResponse(response config.Response) MiddlewareOption { - return func(m *Middleware) { - m.response = response - } -} - -func WithFileSystem(fs afero.Fs) MiddlewareOption { - return func(m *Middleware) { - m.fs = fs - } -} - -func WithAfter(after func(duration time.Duration) <-chan time.Time) MiddlewareOption { - return func(m *Middleware) { - m.after = after - } -} diff --git a/internal/handler/mock/serve_file_content.go b/internal/handler/mock/serve_file_content.go index 8691da4c..334958d8 100644 --- a/internal/handler/mock/serve_file_content.go +++ b/internal/handler/mock/serve_file_content.go @@ -6,9 +6,9 @@ import ( "os" ) -func (m *Middleware) serveFileContent(writer http.ResponseWriter, request *http.Request) error { - fileName := m.response.File - file, err := m.fs.OpenFile(fileName, os.O_RDONLY, os.ModePerm) +func (h *Handler) serveFileContent(writer http.ResponseWriter, request *http.Request) error { + fileName := h.response.File + file, err := h.fs.OpenFile(fileName, os.O_RDONLY, os.ModePerm) if err != nil { return fmt.Errorf("filed to open file %s: %w", fileName, err) } diff --git a/internal/handler/mock/serve_raw_content.go b/internal/handler/mock/serve_raw_content.go index c0db4cbb..499673f6 100644 --- a/internal/handler/mock/serve_raw_content.go +++ b/internal/handler/mock/serve_raw_content.go @@ -7,8 +7,8 @@ import ( "github.com/go-http-utils/headers" ) -func (m *Middleware) serveRawContent(writer http.ResponseWriter) { - response := m.response +func (h *Handler) serveRawContent(writer http.ResponseWriter) { + response := h.response header := writer.Header() if len(header.Get(headers.ContentType)) == 0 { contentType := http.DetectContentType([]byte(response.Raw)) diff --git a/internal/handler/mocked_routes.go b/internal/handler/mocked_routes.go index 4e5c4b39..d8f72004 100644 --- a/internal/handler/mocked_routes.go +++ b/internal/handler/mocked_routes.go @@ -5,7 +5,7 @@ import ( "github.com/gorilla/mux" ) -func (m *RequestHandler) makeMockedRoutes(router *mux.Router, mocks config.Mocks) { +func (h *RequestHandler) makeMockedRoutes(router *mux.Router, mocks config.Mocks) { var defaultMocks config.Mocks for _, mockDef := range mocks { @@ -15,7 +15,7 @@ func (m *RequestHandler) makeMockedRoutes(router *mux.Router, mocks config.Mocks setMethod(route, mockDef.Method) setQueries(route, mockDef.Queries) setHeaders(route, mockDef.Headers) - route.Handler(m.createHandler(mockDef.Response)) + route.Handler(h.createHandler(mockDef.Response)) } else { defaultMocks = append(defaultMocks, mockDef) } @@ -24,6 +24,6 @@ func (m *RequestHandler) makeMockedRoutes(router *mux.Router, mocks config.Mocks for _, mockDef := range defaultMocks { route := router.NewRoute() setPath(route, mockDef.Path) - route.Handler(m.createHandler(mockDef.Response)) + route.Handler(h.createHandler(mockDef.Response)) } } diff --git a/internal/handler/proxy/middleware.go b/internal/handler/proxy/handler.go similarity index 73% rename from internal/handler/proxy/middleware.go rename to internal/handler/proxy/handler.go index 8211526c..7598f215 100644 --- a/internal/handler/proxy/middleware.go +++ b/internal/handler/proxy/handler.go @@ -12,7 +12,7 @@ import ( ) type Handler struct { - replacers contracts.URLReplacerFactory + replacers urlreplacer.ReplacerFactory http contracts.HTTPClient logger contracts.Logger } @@ -31,35 +31,35 @@ func NewProxyHandler(options ...HandlerOption) *Handler { return middleware } -func (m *Handler) ServeHTTP(response http.ResponseWriter, request *http.Request) { - if err := m.handle(response, request); err != nil { +func (h *Handler) ServeHTTP(response contracts.ResponseWriter, request *contracts.Request) { + if err := h.handle(response, request); err != nil { infra.HTTPError(response, err) } } -func (m *Handler) handle(resp http.ResponseWriter, req *http.Request) error { +func (h *Handler) handle(resp http.ResponseWriter, req *http.Request) error { if strings.EqualFold(req.Method, http.MethodOptions) { - return m.makeOptionsResponse(resp, req) + return h.makeOptionsResponse(resp, req) } - targetReplacer, sourceReplacer, err := m.replacers.Make(req.URL) + targetReplacer, sourceReplacer, err := h.replacers.Make(req.URL) if err != nil { return fmt.Errorf("failed to transform general url: %w", err) } - originalRequest, err := m.makeOriginalRequest(req, targetReplacer) + originalRequest, err := h.makeOriginalRequest(req, targetReplacer) if err != nil { return fmt.Errorf("failed to create reuest to original source: %w", err) } - originalResponse, err := m.executeQuery(originalRequest) + originalResponse, err := h.executeQuery(originalRequest) if err != nil { return err } defer helpers.CloseSafe(originalResponse.Body) - err = m.makeUncorsResponse(originalResponse, resp, sourceReplacer) + err = h.makeUncorsResponse(originalResponse, resp, sourceReplacer) if err != nil { return fmt.Errorf("failed to make uncors response: %w", err) } @@ -67,12 +67,12 @@ func (m *Handler) handle(resp http.ResponseWriter, req *http.Request) error { return nil } -func (m *Handler) executeQuery(request *http.Request) (*http.Response, error) { - originalResponse, err := m.http.Do(request) +func (h *Handler) executeQuery(request *http.Request) (*http.Response, error) { + originalResponse, err := h.http.Do(request) if err != nil { return nil, fmt.Errorf("failed to do reuest: %w", err) } - m.logger.PrintResponse(originalResponse) + h.logger.PrintResponse(originalResponse.Request, originalResponse.StatusCode) return originalResponse, nil } diff --git a/internal/handler/proxy/middleware_test.go b/internal/handler/proxy/handler_test.go similarity index 93% rename from internal/handler/proxy/middleware_test.go rename to internal/handler/proxy/handler_test.go index 077c5628..9e38ac47 100644 --- a/internal/handler/proxy/middleware_test.go +++ b/internal/handler/proxy/handler_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/handler/proxy" "github.com/evg4b/uncors/internal/helpers" "github.com/evg4b/uncors/internal/urlreplacer" @@ -20,7 +21,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestProxyMiddleware(t *testing.T) { +func TestProxyHandler(t *testing.T) { replacerFactory, err := urlreplacer.NewURLReplacerFactory(config.Mappings{ {From: "http://premium.local.com", To: "https://premium.api.com"}, }) @@ -65,7 +66,7 @@ func TestProxyMiddleware(t *testing.T) { } }) - proc := proxy.NewProxyHandler( + handler := proxy.NewProxyHandler( proxy.WithHTTPClient(httpClient), proxy.WithURLReplacerFactory(replacerFactory), proxy.WithLogger(mocks.NewNoopLogger(t)), @@ -80,7 +81,7 @@ func TestProxyMiddleware(t *testing.T) { req.Header.Add(testCase.headerKey, testCase.URL) - proc.ServeHTTP(httptest.NewRecorder(), req) + handler.ServeHTTP(contracts.WrapResponseWriter(httptest.NewRecorder()), req) }) } }) @@ -118,7 +119,7 @@ func TestProxyMiddleware(t *testing.T) { } }) - proc := proxy.NewProxyHandler( + handler := proxy.NewProxyHandler( proxy.WithHTTPClient(httpClient), proxy.WithURLReplacerFactory(replacerFactory), proxy.WithLogger(mocks.NewNoopLogger(t)), @@ -133,7 +134,7 @@ func TestProxyMiddleware(t *testing.T) { recorder := httptest.NewRecorder() - proc.ServeHTTP(recorder, req) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), req) assert.Equal(t, testCase.expectedURL, recorder.Header().Get(testCase.headerKey)) }) @@ -152,7 +153,7 @@ func TestProxyMiddleware(t *testing.T) { } }) - proc := proxy.NewProxyHandler( + handler := proxy.NewProxyHandler( proxy.WithHTTPClient(httpClient), proxy.WithURLReplacerFactory(replacerFactory), proxy.WithLogger(mocks.NewNoopLogger(t)), @@ -166,7 +167,7 @@ func TestProxyMiddleware(t *testing.T) { recorder := httptest.NewRecorder() - proc.ServeHTTP(recorder, req) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), req) header := recorder.Header() assert.Equal(t, "*", header.Get(headers.AccessControlAllowOrigin)) @@ -179,7 +180,7 @@ func TestProxyMiddleware(t *testing.T) { }) t.Run("OPTIONS request handling", func(t *testing.T) { - middleware := proxy.NewProxyHandler( + handler := proxy.NewProxyHandler( proxy.WithHTTPClient(http.DefaultClient), proxy.WithURLReplacerFactory(replacerFactory), proxy.WithLogger(mocks.NewNoopLogger(t)), @@ -240,7 +241,7 @@ func TestProxyMiddleware(t *testing.T) { req, err := http.NewRequestWithContext(context.TODO(), http.MethodOptions, "/", nil) testutils.CheckNoError(t, err) - middleware.ServeHTTP(recorder, req) + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), req) assert.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, testCase.expected, recorder.Header()) diff --git a/internal/handler/proxy/option.go b/internal/handler/proxy/option.go index 87c55677..4f4d7edf 100644 --- a/internal/handler/proxy/option.go +++ b/internal/handler/proxy/option.go @@ -6,12 +6,9 @@ import ( "github.com/evg4b/uncors/internal/infra" ) -func (m *Handler) makeOptionsResponse(writer http.ResponseWriter, req *http.Request) error { +func (h *Handler) makeOptionsResponse(writer http.ResponseWriter, req *http.Request) error { infra.WriteCorsHeaders(writer.Header()) - m.logger.PrintResponse(&http.Response{ - StatusCode: http.StatusOK, - Request: req, - }) + h.logger.PrintResponse(req, http.StatusOK) return nil } diff --git a/internal/handler/proxy/options.go b/internal/handler/proxy/options.go index ded7adbd..d91a6559 100644 --- a/internal/handler/proxy/options.go +++ b/internal/handler/proxy/options.go @@ -2,11 +2,12 @@ package proxy import ( "github.com/evg4b/uncors/internal/contracts" + "github.com/evg4b/uncors/internal/urlreplacer" ) type HandlerOption = func(*Handler) -func WithURLReplacerFactory(replacerFactory contracts.URLReplacerFactory) HandlerOption { +func WithURLReplacerFactory(replacerFactory urlreplacer.ReplacerFactory) HandlerOption { return func(m *Handler) { m.replacers = replacerFactory } diff --git a/internal/handler/proxy/request.go b/internal/handler/proxy/request.go index d585e838..eee14160 100644 --- a/internal/handler/proxy/request.go +++ b/internal/handler/proxy/request.go @@ -8,7 +8,7 @@ import ( "github.com/go-http-utils/headers" ) -func (m *Handler) makeOriginalRequest( +func (h *Handler) makeOriginalRequest( req *http.Request, replacer *urlreplacer.Replacer, ) (*http.Request, error) { diff --git a/internal/handler/proxy/responce.go b/internal/handler/proxy/responce.go index a87a2b3d..de2059e4 100644 --- a/internal/handler/proxy/responce.go +++ b/internal/handler/proxy/responce.go @@ -10,7 +10,7 @@ import ( "github.com/go-http-utils/headers" ) -func (m *Handler) makeUncorsResponse( +func (h *Handler) makeUncorsResponse( original *http.Response, target http.ResponseWriter, replacer *urlreplacer.Replacer, diff --git a/internal/handler/static/file.go b/internal/handler/static/file.go index 7d468dc8..e93c969b 100644 --- a/internal/handler/static/file.go +++ b/internal/handler/static/file.go @@ -11,14 +11,14 @@ import ( var errNorHandled = errors.New("request is not handled") -func (m *Middleware) openFile(filePath string) (afero.File, os.FileInfo, error) { - file, err := m.fs.Open(filePath) +func (h *Handler) openFile(filePath string) (afero.File, os.FileInfo, error) { + file, err := h.fs.Open(filePath) if err != nil { if !errors.Is(err, fs.ErrNotExist) { return nil, nil, fmt.Errorf("filed to open file: %w", err) } - indexFile, err := m.openIndexFile() + indexFile, err := h.openIndexFile() if err != nil { return nil, nil, err } @@ -32,7 +32,7 @@ func (m *Middleware) openFile(filePath string) (afero.File, os.FileInfo, error) } if stat.IsDir() { - indexFile, err := m.openIndexFile() + indexFile, err := h.openIndexFile() if err != nil { return file, stat, err } @@ -49,12 +49,12 @@ func (m *Middleware) openFile(filePath string) (afero.File, os.FileInfo, error) return file, stat, nil } -func (m *Middleware) openIndexFile() (afero.File, error) { - if len(m.index) == 0 { +func (h *Handler) openIndexFile() (afero.File, error) { + if len(h.index) == 0 { return nil, errNorHandled } - file, err := m.fs.Open(m.index) + file, err := h.fs.Open(h.index) if err != nil { return nil, fmt.Errorf("filed to open index file: %w", err) } diff --git a/internal/handler/static/middleware.go b/internal/handler/static/middleware.go index c01a302b..4ec0d82d 100644 --- a/internal/handler/static/middleware.go +++ b/internal/handler/static/middleware.go @@ -12,34 +12,34 @@ import ( "github.com/spf13/afero" ) -type Middleware struct { +type Handler struct { fs afero.Fs - next http.Handler + next contracts.Handler index string logger contracts.Logger prefix string } -func NewStaticMiddleware(options ...MiddlewareOption) *Middleware { - middleware := &Middleware{} +func NewStaticHandler(options ...HandlerOption) *Handler { + handler := &Handler{} for _, option := range options { - option(middleware) + option(handler) } - return middleware + return handler } -func (m *Middleware) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - response := WrapResponseWriter(writer) +func (h *Handler) ServeHTTP(writer contracts.ResponseWriter, request *contracts.Request) { + response := contracts.WrapResponseWriter(writer) - filePath := m.extractFilePath(request) - file, stat, err := m.openFile(filePath) + filePath := h.extractFilePath(request) + file, stat, err := h.openFile(filePath) defer helpers.CloseSafe(file) if err != nil { if errors.Is(err, errNorHandled) { - m.next.ServeHTTP(response, request) + h.next.ServeHTTP(response, request) } else { infra.HTTPError(response, err) } @@ -48,14 +48,11 @@ func (m *Middleware) ServeHTTP(writer http.ResponseWriter, request *http.Request } http.ServeContent(response, request, stat.Name(), stat.ModTime(), file) - m.logger.PrintResponse(&http.Response{ - StatusCode: response.StatusCode, - Request: request, - }) + h.logger.PrintResponse(request, response.StatusCode()) } -func (m *Middleware) extractFilePath(request *http.Request) string { - filePath := strings.TrimPrefix(request.URL.Path, m.prefix) +func (h *Handler) extractFilePath(request *http.Request) string { + filePath := strings.TrimPrefix(request.URL.Path, h.prefix) if !strings.HasPrefix(filePath, "/") { filePath = "/" + filePath } diff --git a/internal/handler/static/middleware_options.go b/internal/handler/static/middleware_options.go index ac9e4083..5e4833a9 100644 --- a/internal/handler/static/middleware_options.go +++ b/internal/handler/static/middleware_options.go @@ -1,40 +1,38 @@ package static import ( - "net/http" - "github.com/evg4b/uncors/internal/contracts" "github.com/spf13/afero" ) -type MiddlewareOption = func(*Middleware) +type HandlerOption = func(*Handler) -func WithFileSystem(fs afero.Fs) MiddlewareOption { - return func(m *Middleware) { - m.fs = fs +func WithFileSystem(fs afero.Fs) HandlerOption { + return func(h *Handler) { + h.fs = fs } } -func WithIndex(index string) MiddlewareOption { - return func(m *Middleware) { - m.index = index +func WithIndex(index string) HandlerOption { + return func(h *Handler) { + h.index = index } } -func WithNext(next http.Handler) MiddlewareOption { - return func(m *Middleware) { - m.next = next +func WithNext(next contracts.Handler) HandlerOption { + return func(h *Handler) { + h.next = next } } -func WithLogger(logger contracts.Logger) MiddlewareOption { - return func(m *Middleware) { - m.logger = logger +func WithLogger(logger contracts.Logger) HandlerOption { + return func(h *Handler) { + h.logger = logger } } -func WithPrefix(prefix string) MiddlewareOption { - return func(m *Middleware) { - m.prefix = prefix +func WithPrefix(prefix string) HandlerOption { + return func(h *Handler) { + h.prefix = prefix } } diff --git a/internal/handler/static/middleware_test.go b/internal/handler/static/middleware_test.go index 2249d983..500f0e9c 100644 --- a/internal/handler/static/middleware_test.go +++ b/internal/handler/static/middleware_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/handler/static" "github.com/evg4b/uncors/internal/sfmt" "github.com/evg4b/uncors/testing/mocks" @@ -98,10 +99,10 @@ func TestMiddleware(t *testing.T) { t.Run("index file is not configured", func(t *testing.T) { const testHTTPStatusCode = 999 const testHTTPBody = "this is tests response" - middleware := static.NewStaticMiddleware( + handler := static.NewStaticHandler( static.WithFileSystem(fs), static.WithLogger(loggerMock), - static.WithNext(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) { + static.WithNext(contracts.HandlerFunc(func(writer contracts.ResponseWriter, _ *contracts.Request) { writer.WriteHeader(testHTTPStatusCode) sfmt.Fprint(writer, testHTTPBody) })), @@ -114,7 +115,7 @@ func TestMiddleware(t *testing.T) { requestURI, err := url.Parse(testCase.path) testutils.CheckNoError(t, err) - middleware.ServeHTTP(recorder, &http.Request{ + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), &http.Request{ Method: http.MethodGet, URL: requestURI, }) @@ -132,7 +133,7 @@ func TestMiddleware(t *testing.T) { requestURI, err := url.Parse(testCase.path) testutils.CheckNoError(t, err) - middleware.ServeHTTP(recorder, &http.Request{ + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), &http.Request{ Method: http.MethodGet, URL: requestURI, }) @@ -145,13 +146,11 @@ func TestMiddleware(t *testing.T) { }) t.Run("index file is configured", func(t *testing.T) { - middleware := static.NewStaticMiddleware( + handler := static.NewStaticHandler( static.WithFileSystem(fs), static.WithLogger(loggerMock), static.WithIndex(indexHTML), - static.WithNext(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { - panic("should not be called") - })), + static.WithNext(mocks.FailNowMock(t)), ) t.Run("send index file", func(t *testing.T) { @@ -161,7 +160,7 @@ func TestMiddleware(t *testing.T) { requestURI, err := url.Parse(testCase.path) testutils.CheckNoError(t, err) - middleware.ServeHTTP(recorder, &http.Request{ + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), &http.Request{ Method: http.MethodGet, URL: requestURI, }) @@ -179,7 +178,7 @@ func TestMiddleware(t *testing.T) { requestURI, err := url.Parse(testCase.path) testutils.CheckNoError(t, err) - middleware.ServeHTTP(recorder, &http.Request{ + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), &http.Request{ Method: http.MethodGet, URL: requestURI, }) @@ -191,20 +190,18 @@ func TestMiddleware(t *testing.T) { }) t.Run("index file doesn't exists", func(t *testing.T) { - middleware := static.NewStaticMiddleware( + handler := static.NewStaticHandler( static.WithFileSystem(fs), static.WithLogger(loggerMock), static.WithIndex("/not-exists.html"), - static.WithNext(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { - panic("should not be called") - })), + static.WithNext(mocks.FailNowMock(t)), ) recorder := httptest.NewRecorder() requestURI, err := url.Parse("/options/") testutils.CheckNoError(t, err) - middleware.ServeHTTP(recorder, &http.Request{ + handler.ServeHTTP(contracts.WrapResponseWriter(recorder), &http.Request{ Method: http.MethodGet, URL: requestURI, }) diff --git a/internal/handler/static/response_writer.go b/internal/handler/static/response_writer.go deleted file mode 100644 index bd2ae83b..00000000 --- a/internal/handler/static/response_writer.go +++ /dev/null @@ -1,19 +0,0 @@ -package static - -import ( - "net/http" -) - -type ResponseWriter struct { - StatusCode int - http.ResponseWriter -} - -func WrapResponseWriter(writer http.ResponseWriter) *ResponseWriter { - return &ResponseWriter{ResponseWriter: writer} -} - -func (r *ResponseWriter) WriteHeader(statusCode int) { - r.StatusCode = statusCode - r.ResponseWriter.WriteHeader(statusCode) -} diff --git a/internal/handler/static_routes.go b/internal/handler/static_routes.go index 90837076..9658c25a 100644 --- a/internal/handler/static_routes.go +++ b/internal/handler/static_routes.go @@ -5,13 +5,18 @@ import ( "strings" "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/handler/static" "github.com/evg4b/uncors/internal/ui" "github.com/gorilla/mux" "github.com/spf13/afero" ) -func (m *RequestHandler) makeStaticRoutes(router *mux.Router, statics config.StaticDirectories, next http.Handler) { +func (h *RequestHandler) makeStaticRoutes( + router *mux.Router, + statics config.StaticDirectories, + next contracts.Handler, +) { for _, staticDir := range statics { clearPath := strings.TrimSuffix(staticDir.Path, "/") path := clearPath + "/" @@ -21,14 +26,15 @@ func (m *RequestHandler) makeStaticRoutes(router *mux.Router, statics config.Sta Handler(http.RedirectHandler(path, http.StatusTemporaryRedirect)) route := router.NewRoute() - handler := static.NewStaticMiddleware( - static.WithFileSystem(afero.NewBasePathFs(m.fs, staticDir.Dir)), + handler := static.NewStaticHandler( + static.WithFileSystem(afero.NewBasePathFs(h.fs, staticDir.Dir)), static.WithIndex(staticDir.Index), static.WithNext(next), static.WithLogger(ui.StaticLogger), static.WithPrefix(path), ) - route.PathPrefix(path).Handler(handler) + httpHandler := contracts.CastToHTTPHandler(handler) + route.PathPrefix(path).Handler(httpHandler) } } diff --git a/internal/handler/uncors_handler.go b/internal/handler/uncors_handler.go index ac8b5225..9a3ba5d0 100644 --- a/internal/handler/uncors_handler.go +++ b/internal/handler/uncors_handler.go @@ -2,6 +2,7 @@ package handler import ( "errors" + "fmt" "net/http" "strings" "time" @@ -10,26 +11,29 @@ import ( "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/handler/mock" "github.com/evg4b/uncors/internal/handler/proxy" + "github.com/evg4b/uncors/internal/helpers" "github.com/evg4b/uncors/internal/infra" "github.com/evg4b/uncors/internal/sfmt" "github.com/evg4b/uncors/internal/ui" + "github.com/evg4b/uncors/internal/urlreplacer" "github.com/evg4b/uncors/pkg/urlx" "github.com/gorilla/mux" "github.com/spf13/afero" ) type RequestHandler struct { - router *mux.Router - fs afero.Fs - logger contracts.Logger - mappings config.Mappings - replacerFactory contracts.URLReplacerFactory - httpClient contracts.HTTPClient + router *mux.Router + fs afero.Fs + logger contracts.Logger + mappings config.Mappings + replacerFactory urlreplacer.ReplacerFactory + httpClient contracts.HTTPClient + cacheMiddlewareFactory cacheMiddlewareFactory } var errHostNotMapped = errors.New("host not mapped") -func NewUncorsRequestHandler(options ...UncorsRequestHandlerOption) *RequestHandler { +func NewUncorsRequestHandler(options ...RequestHandlerOption) *RequestHandler { handler := &RequestHandler{ router: mux.NewRouter(), mappings: config.Mappings{}, @@ -39,13 +43,15 @@ func NewUncorsRequestHandler(options ...UncorsRequestHandlerOption) *RequestHand option(handler) } + helpers.AssertIsDefined(handler.cacheMiddlewareFactory, "Cache middleware is not set") + proxyHandler := proxy.NewProxyHandler( proxy.WithURLReplacerFactory(handler.replacerFactory), proxy.WithHTTPClient(handler.httpClient), proxy.WithLogger(ui.ProxyLogger), ) - for _, mapping := range handler.mappings { + for index, mapping := range handler.mappings { uri, err := urlx.Parse(mapping.From) if err != nil { panic(err) @@ -60,32 +66,42 @@ func NewUncorsRequestHandler(options ...UncorsRequestHandlerOption) *RequestHand handler.makeStaticRoutes(router, mapping.Statics, proxyHandler) handler.makeMockedRoutes(router, mapping.Mocks) - setDefaultHandler(router, proxyHandler) + + var defaultHandler contracts.Handler = proxyHandler + if len(mapping.Cache) > 0 { + cachePrefix := fmt.Sprintf("mapping_%d", index) + cacheMiddleware := handler.cacheMiddlewareFactory(cachePrefix, mapping.Cache) + defaultHandler = cacheMiddleware.Wrap(proxyHandler) + } + + setDefaultHandler(router, defaultHandler) } - setDefaultHandler(handler.router, http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) { + setDefaultHandler(handler.router, contracts.HandlerFunc(func(writer contracts.ResponseWriter, _ *http.Request) { infra.HTTPError(writer, errHostNotMapped) })) return handler } -func (m *RequestHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - m.router.ServeHTTP(writer, request) +func (h *RequestHandler) ServeHTTP(writer contracts.ResponseWriter, request *contracts.Request) { + h.router.ServeHTTP(writer, request) } -func (m *RequestHandler) createHandler(response config.Response) *mock.Middleware { - return mock.NewMockMiddleware( - mock.WithLogger(ui.MockLogger), - mock.WithResponse(response), - mock.WithFileSystem(m.fs), - mock.WithAfter(time.After), +func (h *RequestHandler) createHandler(response config.Response) http.Handler { + return contracts.CastToHTTPHandler( + mock.NewMockHandler( + mock.WithLogger(ui.MockLogger), + mock.WithResponse(response), + mock.WithFileSystem(h.fs), + mock.WithAfter(time.After), + ), ) } -func setDefaultHandler(router *mux.Router, handler http.Handler) { - router.NotFoundHandler = handler - router.MethodNotAllowedHandler = handler +func setDefaultHandler(router *mux.Router, handler contracts.Handler) { + router.NotFoundHandler = contracts.CastToHTTPHandler(handler) + router.MethodNotAllowedHandler = contracts.CastToHTTPHandler(handler) } const wildcard = "*" diff --git a/internal/handler/uncors_handler_options.go b/internal/handler/uncors_handler_options.go index ac3bdaec..f5ad8449 100644 --- a/internal/handler/uncors_handler_options.go +++ b/internal/handler/uncors_handler_options.go @@ -3,37 +3,46 @@ package handler import ( "github.com/evg4b/uncors/internal/config" "github.com/evg4b/uncors/internal/contracts" + "github.com/evg4b/uncors/internal/urlreplacer" "github.com/spf13/afero" ) -type UncorsRequestHandlerOption = func(*RequestHandler) +type RequestHandlerOption = func(*RequestHandler) -func WithLogger(logger contracts.Logger) UncorsRequestHandlerOption { - return func(m *RequestHandler) { - m.logger = logger +func WithLogger(logger contracts.Logger) RequestHandlerOption { + return func(h *RequestHandler) { + h.logger = logger } } -func WithFileSystem(fs afero.Fs) UncorsRequestHandlerOption { - return func(m *RequestHandler) { - m.fs = fs +func WithFileSystem(fs afero.Fs) RequestHandlerOption { + return func(h *RequestHandler) { + h.fs = fs } } -func WithURLReplacerFactory(replacerFactory contracts.URLReplacerFactory) UncorsRequestHandlerOption { - return func(m *RequestHandler) { - m.replacerFactory = replacerFactory +func WithURLReplacerFactory(replacerFactory urlreplacer.ReplacerFactory) RequestHandlerOption { + return func(h *RequestHandler) { + h.replacerFactory = replacerFactory } } -func WithHTTPClient(client contracts.HTTPClient) UncorsRequestHandlerOption { - return func(m *RequestHandler) { - m.httpClient = client +func WithHTTPClient(client contracts.HTTPClient) RequestHandlerOption { + return func(h *RequestHandler) { + h.httpClient = client } } -func WithMappings(mappings config.Mappings) UncorsRequestHandlerOption { - return func(m *RequestHandler) { - m.mappings = mappings +func WithMappings(mappings config.Mappings) RequestHandlerOption { + return func(h *RequestHandler) { + h.mappings = mappings + } +} + +type cacheMiddlewareFactory = func(key string, globs config.CacheGlobs) contracts.MiddlewareHandler + +func WithCacheMiddlewareFactory(factory cacheMiddlewareFactory) RequestHandlerOption { + return func(h *RequestHandler) { + h.cacheMiddlewareFactory = factory } } diff --git a/internal/handler/uncors_handler_test.go b/internal/handler/uncors_handler_test.go index f787f6ba..e954e854 100644 --- a/internal/handler/uncors_handler_test.go +++ b/internal/handler/uncors_handler_test.go @@ -8,7 +8,9 @@ import ( "testing" "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/handler" + "github.com/evg4b/uncors/internal/handler/cache" "github.com/evg4b/uncors/internal/helpers" "github.com/evg4b/uncors/internal/log" "github.com/evg4b/uncors/internal/sfmt" @@ -17,6 +19,7 @@ import ( "github.com/evg4b/uncors/testing/testconstants" "github.com/evg4b/uncors/testing/testutils" "github.com/go-http-utils/headers" + goCache "github.com/patrickmn/go-cache" "github.com/stretchr/testify/assert" ) @@ -41,6 +44,17 @@ var ( userIDHeader = "User-Id" ) +func cacheFactory(t *testing.T) func(key string, globs config.CacheGlobs) contracts.MiddlewareHandler { + return func(key string, globs config.CacheGlobs) contracts.MiddlewareHandler { + return cache.NewMiddleware( + cache.WithPrefix(key), + cache.WithGlobs(globs), + cache.WithLogger(mocks.NewNoopLogger(t)), + cache.WithCacheStorage(goCache.New(goCache.DefaultExpiration, goCache.DefaultExpiration)), + ) + } +} + func TestUncorsRequestHandler(t *testing.T) { log.DisableOutput() fs := testutils.FsFromMap(t, map[string]string{ @@ -115,12 +129,13 @@ func TestUncorsRequestHandler(t *testing.T) { panic(sfmt.Sprintf("incorrect request: %s", request.URL.Path)) }) - hand := handler.NewUncorsRequestHandler( + uncorsHandler := handler.NewUncorsRequestHandler( handler.WithLogger(mocks.NewLoggerMock(t)), handler.WithFileSystem(fs), handler.WithURLReplacerFactory(factory), handler.WithHTTPClient(httpMock), handler.WithMappings(mappings), + handler.WithCacheMiddlewareFactory(cacheFactory(t)), ) t.Run("statics directory", func(t *testing.T) { @@ -153,7 +168,7 @@ func TestUncorsRequestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, testCase.url, nil) helpers.NormaliseRequest(request) - hand.ServeHTTP(recorder, request) + uncorsHandler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, 200, recorder.Code) assert.Equal(t, testCase.expected, testutils.ReadBody(t, recorder)) @@ -166,7 +181,7 @@ func TestUncorsRequestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "http://localhost/cc/unknown.html", nil) helpers.NormaliseRequest(request) - hand.ServeHTTP(recorder, request) + uncorsHandler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, indexHTML, testutils.ReadBody(t, recorder)) @@ -177,7 +192,7 @@ func TestUncorsRequestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "http://localhost/pnp/unknown.html", nil) helpers.NormaliseRequest(request) - hand.ServeHTTP(recorder, request) + uncorsHandler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, http.StatusInternalServerError, recorder.Code) expectedMessage := "filed to open index file: open /assets/index.php: file does not exist" @@ -209,7 +224,7 @@ func TestUncorsRequestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, testCase.url, nil) helpers.NormaliseRequest(request) - hand.ServeHTTP(recorder, request) + uncorsHandler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, testCase.expected, testutils.ReadBody(t, recorder)) @@ -222,7 +237,7 @@ func TestUncorsRequestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "http://localhost/img/original.png", nil) helpers.NormaliseRequest(request) - hand.ServeHTTP(recorder, request) + uncorsHandler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, "original.png", testutils.ReadBody(t, recorder)) @@ -263,7 +278,7 @@ func TestUncorsRequestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, testCase.url, nil) helpers.NormaliseRequest(request) - hand.ServeHTTP(recorder, request) + uncorsHandler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, testCase.expectedCode, recorder.Code) assert.Equal(t, testCase.expected, testutils.ReadBody(t, recorder)) @@ -276,7 +291,7 @@ func TestUncorsRequestHandler(t *testing.T) { request := httptest.NewRequest(http.MethodGet, "http://localhost/api/mocks/4", nil) helpers.NormaliseRequest(request) - hand.ServeHTTP(recorder, request) + uncorsHandler.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, http.StatusInternalServerError, recorder.Code) expectedMessage := "filed to open file /unknown.json: open /unknown.json: file does not exist" @@ -293,7 +308,7 @@ func TestMockMiddleware(t *testing.T) { t.Run("where mock method is not set allow method", func(t *testing.T) { middleware := handler.NewUncorsRequestHandler( handler.WithHTTPClient(mocks.NewHTTPClientMock(t)), - handler.WithURLReplacerFactory(mocks.NewURLReplacerFactoryMock(t)), + handler.WithURLReplacerFactory(mocks.NewReplacerFactoryMock(t)), handler.WithLogger(logger), handler.WithMappings(config.Mappings{ { @@ -310,6 +325,7 @@ func TestMockMiddleware(t *testing.T) { }, }, }), + handler.WithCacheMiddlewareFactory(cacheFactory(t)), ) methods := []string{ @@ -327,7 +343,7 @@ func TestMockMiddleware(t *testing.T) { request := httptest.NewRequest(method, api, nil) recorder := httptest.NewRecorder() - middleware.ServeHTTP(recorder, request) + middleware.ServeHTTP(contracts.WrapResponseWriter(recorder), request) body := testutils.ReadBody(t, recorder) assert.Equal(t, http.StatusOK, recorder.Code) @@ -364,6 +380,7 @@ func TestMockMiddleware(t *testing.T) { handler.WithURLReplacerFactory(factory), handler.WithLogger(logger), handler.WithMappings(mappings), + handler.WithCacheMiddlewareFactory(cacheFactory(t)), ) t.Run("method is not matched", func(t *testing.T) { @@ -381,7 +398,7 @@ func TestMockMiddleware(t *testing.T) { request := httptest.NewRequest(method, api, nil) recorder := httptest.NewRecorder() - middleware.ServeHTTP(recorder, request) + middleware.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, expectedCode, recorder.Code) assert.Equal(t, expectedBody, testutils.ReadBody(t, recorder)) @@ -392,7 +409,7 @@ func TestMockMiddleware(t *testing.T) { request := httptest.NewRequest(http.MethodOptions, api, nil) recorder := httptest.NewRecorder() - middleware.ServeHTTP(recorder, request) + middleware.ServeHTTP(contracts.WrapResponseWriter(recorder), request) assert.Equal(t, http.StatusOK, recorder.Code) assert.Equal(t, "", testutils.ReadBody(t, recorder)) @@ -403,7 +420,7 @@ func TestMockMiddleware(t *testing.T) { request := httptest.NewRequest(http.MethodPut, api, nil) recorder := httptest.NewRecorder() - middleware.ServeHTTP(recorder, request) + middleware.ServeHTTP(contracts.WrapResponseWriter(recorder), request) body := testutils.ReadBody(t, recorder) assert.Equal(t, http.StatusOK, recorder.Code) @@ -462,6 +479,7 @@ func TestMockMiddleware(t *testing.T) { handler.WithURLReplacerFactory(factory), handler.WithLogger(logger), handler.WithMappings(mappings), + handler.WithCacheMiddlewareFactory(cacheFactory(t)), ) tests := []struct { @@ -512,7 +530,7 @@ func TestMockMiddleware(t *testing.T) { request := httptest.NewRequest(http.MethodGet, testCase.url, nil) recorder := httptest.NewRecorder() - middleware.ServeHTTP(recorder, request) + middleware.ServeHTTP(contracts.WrapResponseWriter(recorder), request) body := testutils.ReadBody(t, recorder) assert.Equal(t, testCase.statusCode, recorder.Code) @@ -524,7 +542,7 @@ func TestMockMiddleware(t *testing.T) { t.Run("query handling", func(t *testing.T) { middleware := handler.NewUncorsRequestHandler( handler.WithHTTPClient(mocks.NewHTTPClientMock(t)), - handler.WithURLReplacerFactory(mocks.NewURLReplacerFactoryMock(t)), + handler.WithURLReplacerFactory(mocks.NewReplacerFactoryMock(t)), handler.WithLogger(logger), handler.WithMappings(config.Mappings{ {From: "*", To: "*", Mocks: config.Mocks{ @@ -558,6 +576,7 @@ func TestMockMiddleware(t *testing.T) { }, }}, }), + handler.WithCacheMiddlewareFactory(cacheFactory(t)), ) tests := []struct { @@ -608,7 +627,7 @@ func TestMockMiddleware(t *testing.T) { request := httptest.NewRequest(http.MethodGet, testCase.url, nil) recorder := httptest.NewRecorder() - middleware.ServeHTTP(recorder, request) + middleware.ServeHTTP(contracts.WrapResponseWriter(recorder), request) body := testutils.ReadBody(t, recorder) assert.Equal(t, testCase.statusCode, recorder.Code) @@ -620,7 +639,7 @@ func TestMockMiddleware(t *testing.T) { t.Run("header handling", func(t *testing.T) { middleware := handler.NewUncorsRequestHandler( handler.WithHTTPClient(mocks.NewHTTPClientMock(t)), - handler.WithURLReplacerFactory(mocks.NewURLReplacerFactoryMock(t)), + handler.WithURLReplacerFactory(mocks.NewReplacerFactoryMock(t)), handler.WithLogger(logger), handler.WithMappings(config.Mappings{ {From: "*", To: "*", Mocks: config.Mocks{ @@ -654,6 +673,7 @@ func TestMockMiddleware(t *testing.T) { }, }}, }), + handler.WithCacheMiddlewareFactory(cacheFactory(t)), ) tests := []struct { @@ -726,7 +746,7 @@ func TestMockMiddleware(t *testing.T) { } recorder := httptest.NewRecorder() - middleware.ServeHTTP(recorder, request) + middleware.ServeHTTP(contracts.WrapResponseWriter(recorder), request) body := testutils.ReadBody(t, recorder) assert.Equal(t, testCase.statusCode, recorder.Code) diff --git a/internal/helpers/asset.go b/internal/helpers/asset.go index 1430dd46..41f24bad 100644 --- a/internal/helpers/asset.go +++ b/internal/helpers/asset.go @@ -1,9 +1,12 @@ package helpers -import "strings" +import ( + "strings" + "unsafe" +) func AssertIsDefined(value any, message ...string) { - if value == nil { + if (*[2]uintptr)(unsafe.Pointer(&value))[1] == 0 { message := strings.Join(message, " ") if len(message) == 0 { message = "Required variable is not defined" diff --git a/internal/helpers/asset_test.go b/internal/helpers/asset_test.go index b21eeda1..4362be7f 100644 --- a/internal/helpers/asset_test.go +++ b/internal/helpers/asset_test.go @@ -1,6 +1,7 @@ package helpers_test import ( + "net/http" "testing" "github.com/evg4b/uncors/internal/helpers" @@ -9,33 +10,59 @@ import ( func TestAssertIsDefined(t *testing.T) { t.Run("where value is nil", func(t *testing.T) { - tests := []struct { - name string - message []string - expected string - }{ - { - name: "should panic with default message where message is not set", - message: []string{}, - expected: "Required variable is not defined", - }, - { - name: "should panic with custom message where it is set", - message: []string{"Custom message"}, - expected: "Custom message", - }, - { - name: "should panic with concatenation of all passed messages", - message: []string{"This", "is", "custom", "message"}, - expected: "This is custom message", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.PanicsWithValue(t, tt.expected, func() { - helpers.AssertIsDefined(nil, tt.message...) + t.Run("should panic on", func(t *testing.T) { + t.Run("just nil value", func(t *testing.T) { + assert.Panics(t, func() { + helpers.AssertIsDefined(nil) }) }) - } + t.Run("nil value in pointer", func(t *testing.T) { + assert.Panics(t, func() { + helpers.AssertIsDefined((*int64)(nil)) + }) + }) + t.Run("nil value in interface", func(t *testing.T) { + assert.Panics(t, func() { + helpers.AssertIsDefined((http.Handler)(nil)) + }) + }) + t.Run("nil value in function", func(t *testing.T) { + assert.Panics(t, func() { + var f func() + helpers.AssertIsDefined(f) + }) + }) + }) + + t.Run("should panic with ", func(t *testing.T) { + tests := []struct { + name string + message []string + expected string + }{ + { + name: "default message where message is not set", + message: []string{}, + expected: "Required variable is not defined", + }, + { + name: "custom message where it is set", + message: []string{"Custom message"}, + expected: "Custom message", + }, + { + name: "concatenation of all passed messages", + message: []string{"This", "is", "custom", "message"}, + expected: "This is custom message", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.PanicsWithValue(t, tt.expected, func() { + helpers.AssertIsDefined(nil, tt.message...) + }) + }) + } + }) }) } diff --git a/internal/helpers/http.go b/internal/helpers/http.go index 3d345c44..b78d85f0 100644 --- a/internal/helpers/http.go +++ b/internal/helpers/http.go @@ -11,3 +11,23 @@ func NormaliseRequest(request *http.Request) { request.URL.Scheme = "http" } } + +func Is1xxCode(code int) bool { + return 100 <= code && code < 200 +} + +func Is2xxCode(code int) bool { + return 200 <= code && code < 300 +} + +func Is3xxCode(code int) bool { + return 300 <= code && code < 400 +} + +func Is4xxCode(code int) bool { + return 400 <= code && code < 500 +} + +func Is5xxCode(code int) bool { + return 500 <= code && code < 600 +} diff --git a/internal/helpers/http_test.go b/internal/helpers/http_test.go index a0d0d615..2398e868 100644 --- a/internal/helpers/http_test.go +++ b/internal/helpers/http_test.go @@ -2,6 +2,7 @@ package helpers_test import ( "crypto/tls" + "fmt" "net/http" "testing" @@ -53,3 +54,118 @@ func TestNormaliseRequest(t *testing.T) { assert.Equal(t, request.URL.Host, testconstants.Localhost) }) } + +func TestIs1xxCode(t *testing.T) { + cases := []struct { + code int + expected bool + }{ + {http.StatusContinue, true}, + {http.StatusSwitchingProtocols, true}, + {http.StatusOK, false}, + {http.StatusMovedPermanently, false}, + {http.StatusBadRequest, false}, + {http.StatusInternalServerError, false}, + } + + for _, testCase := range cases { + t.Run(fmt.Sprintf("shoul return %t for code %d", testCase.expected, testCase.code), func(t *testing.T) { + actual := helpers.Is1xxCode(testCase.code) + assert.Equal(t, testCase.expected, actual) + }) + } +} + +func TestIs2xxCode(t *testing.T) { + cases := []struct { + code int + expected bool + }{ + {http.StatusOK, true}, + {http.StatusCreated, true}, + {http.StatusAccepted, true}, + {http.StatusSwitchingProtocols, false}, + {http.StatusMultipleChoices, false}, + {http.StatusBadRequest, false}, + {http.StatusInternalServerError, false}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("shoul return %t for code %d", c.expected, c.code), func(t *testing.T) { + actual := helpers.Is2xxCode(c.code) + + assert.Equal(t, c.expected, actual) + }) + } +} + +func TestIs3xxCode(t *testing.T) { + cases := []struct { + code int + expected bool + }{ + {http.StatusMultipleChoices, true}, + {http.StatusMovedPermanently, true}, + {http.StatusFound, true}, + {http.StatusOK, false}, + {http.StatusSwitchingProtocols, false}, + {http.StatusBadRequest, false}, + {http.StatusInternalServerError, false}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("shoul return %t for code %d", c.expected, c.code), func(t *testing.T) { + actual := helpers.Is3xxCode(c.code) + + assert.Equal(t, c.expected, actual) + }) + } +} + +func TestIs4xxCode(t *testing.T) { + cases := []struct { + code int + expected bool + }{ + {http.StatusBadRequest, true}, + {http.StatusUnauthorized, true}, + {http.StatusForbidden, true}, + {http.StatusOK, false}, + {http.StatusSwitchingProtocols, false}, + {http.StatusMultipleChoices, false}, + {http.StatusInternalServerError, false}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("shoul return %t for code %d", c.expected, c.code), func(t *testing.T) { + actual := helpers.Is4xxCode(c.code) + + assert.Equal(t, c.expected, actual) + }) + } +} + +func TestIs5xxCode(t *testing.T) { + cases := []struct { + code int + expected bool + }{ + {http.StatusBadRequest, false}, + {http.StatusUnauthorized, false}, + {http.StatusForbidden, false}, + {http.StatusOK, false}, + {http.StatusSwitchingProtocols, false}, + {http.StatusMultipleChoices, false}, + {http.StatusInternalServerError, true}, + {http.StatusNetworkAuthenticationRequired, true}, + {http.StatusHTTPVersionNotSupported, true}, + } + + for _, c := range cases { + t.Run(fmt.Sprintf("shoul return %t for code %d", c.expected, c.code), func(t *testing.T) { + actual := helpers.Is5xxCode(c.code) + + assert.Equal(t, c.expected, actual) + }) + } +} diff --git a/internal/log/logger.go b/internal/log/logger.go index 09370b17..49807243 100644 --- a/internal/log/logger.go +++ b/internal/log/logger.go @@ -1,8 +1,7 @@ package log import ( - "net/http" - + "github.com/evg4b/uncors/internal/contracts" "github.com/pterm/pterm" ) @@ -73,6 +72,6 @@ func (logger *PrefixedLogger) Debugf(template string, v ...any) { } } -func (logger *PrefixedLogger) PrintResponse(response *http.Response) { - logger.writer.Println(printResponse(response)) +func (logger *PrefixedLogger) PrintResponse(request *contracts.Request, statusCode int) { + logger.writer.Println(printResponse(request, statusCode)) } diff --git a/internal/log/logger_test.go b/internal/log/logger_test.go index 48612101..83e97070 100644 --- a/internal/log/logger_test.go +++ b/internal/log/logger_test.go @@ -7,6 +7,7 @@ import ( "net/url" "testing" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/log" "github.com/evg4b/uncors/testing/testutils" "github.com/pterm/pterm" @@ -110,20 +111,24 @@ func TestPrefixedLogger(t *testing.T) { t.Run("should correctly format", testutils.LogTest(func(t *testing.T, output *bytes.Buffer) { tests := []struct { - name string - response *http.Response - expected string + name string + response *http.Response + request *contracts.Request + statusCode int + expected string }{ { - name: "response with status code 1xx", - response: makeResponseURL(100, http.MethodPost, "/api/info"), + name: "response with status code 1xx", + statusCode: 100, + request: request(http.MethodPost, "/api/info"), expected: "\x1b[30;104m\x1b[30;104m Test \x1b[0m\x1b[0m \x1b[39;49m\x1b[39;49m\x1b[30;46m" + "\x1b[30;46m 100 POST \x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m \x1b[96m\x1b[96mhttps://api.domain" + ".com/api/info\x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m\x1b[0m\x1b[0m\n", }, { - name: "response with success code 2xx", - response: makeResponseURL(200, http.MethodGet, "/help"), + name: "response with success code 2xx", + statusCode: 200, + request: request(http.MethodGet, "/help"), expected: "\x1b[30;104m\x1b[30;104m Test \x1b[0m\x1b[0m \x1b[39;49m\x1b[39;49m\x1b[30;46m" + "\x1b[30;46m 100 POST \x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m \x1b[96m\x1b[96mhttps://api." + "domain.com/api/info\x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m\x1b[0m\x1b[0m\n\x1b[30;104m\x1b" + @@ -132,8 +137,9 @@ func TestPrefixedLogger(t *testing.T) { "[39;49m\x1b[0m\x1b[39;49m\x1b[0m\x1b[0m\n", }, { - name: "response with redirect code 3xx", - response: makeResponseURL(300, http.MethodPatch, "/api/user"), + name: "response with redirect code 3xx", + statusCode: 300, + request: request(http.MethodPatch, "/api/user"), expected: "\x1b[30;104m\x1b[30;104m Test \x1b[0m\x1b[0m \x1b[39;49m\x1b[39;49m\x1b[30;46m\x1b" + "[30;46m 100 POST \x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m \x1b[96m\x1b[96mhttps://api.domain" + ".com/api/info\x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m\x1b[0m\x1b[0m\n\x1b[30;104m\x1b[30;104m " + @@ -144,8 +150,9 @@ func TestPrefixedLogger(t *testing.T) { "[33mhttps://api.domain.com/api/user\x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m\x1b[0m\x1b[0m\n", }, { - name: "response with user request error code 4xx", - response: makeResponseURL(400, http.MethodDelete, "/api/user/permission"), + name: "response with user request error code 4xx", + statusCode: 400, + request: request(http.MethodDelete, "/api/user/permission"), expected: "\x1b[30;104m\x1b[30;104m Test \x1b[0m\x1b[0m \x1b[39;49m\x1b[39;49m\x1b[30;46m" + "\x1b[30;46m 100 POST \x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m \x1b[96m\x1b[96mhttps://api." + "domain.com/api/info\x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m\x1b[0m\x1b[0m\n\x1b[30;104m\x1b" + @@ -160,8 +167,9 @@ func TestPrefixedLogger(t *testing.T) { "\x1b[0m\x1b[0m\n", }, { - name: "response with internal server error code 5xx", - response: makeResponseURL(500, http.MethodPost, "/"), + name: "response with internal server error code 5xx", + statusCode: 500, + request: request(http.MethodPost, "/"), expected: "\x1b[30;104m\x1b[30;104m Test \x1b[0m\x1b[0m \x1b[39;49m\x1b[39;49m\x1b[30;46m\x1b" + "[30;46m 100 POST \x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m \x1b[96m\x1b[96mhttps://api.domain." + "com/api/info\x1b[0m\x1b[39;49m\x1b[0m\x1b[39;49m\x1b[0m\x1b[0m\n\x1b[30;104m\x1b[30;104m" + @@ -180,7 +188,7 @@ func TestPrefixedLogger(t *testing.T) { } for _, testCase := range tests { t.Run(testCase.name, func(t *testing.T) { - logger.PrintResponse(testCase.response) + logger.PrintResponse(testCase.request, testCase.statusCode) assert.Equal(t, testCase.expected, output.String()) }) @@ -189,28 +197,25 @@ func TestPrefixedLogger(t *testing.T) { t.Run("should panic for status code less then 100", testutils.LogTest(func(t *testing.T, output *bytes.Buffer) { assert.Panics(t, func() { - logger.PrintResponse(makeResponseURL(50, http.MethodGet, "/")) + logger.PrintResponse(request(http.MethodGet, "/"), 50) }) })) t.Run("should panic for status code great then 599", testutils.LogTest(func(t *testing.T, output *bytes.Buffer) { assert.Panics(t, func() { - logger.PrintResponse(makeResponseURL(600, http.MethodGet, "/")) + logger.PrintResponse(request(http.MethodGet, "/"), 600) }) })) }) } -func makeResponseURL(code int, method string, path string) *http.Response { - return &http.Response{ - Request: &http.Request{ - Method: method, - URL: &url.URL{ - Scheme: "https", - Host: "api.domain.com", - Path: path, - }, +func request(method string, path string) *http.Request { + return &http.Request{ + Method: method, + URL: &url.URL{ + Scheme: "https", + Host: "api.domain.com", + Path: path, }, - StatusCode: code, } } diff --git a/internal/log/printresponse.go b/internal/log/printresponse.go index 17494644..246eca3d 100644 --- a/internal/log/printresponse.go +++ b/internal/log/printresponse.go @@ -1,25 +1,21 @@ package log import ( - "net/http" - + "github.com/evg4b/uncors/internal/contracts" + "github.com/evg4b/uncors/internal/helpers" "github.com/evg4b/uncors/internal/sfmt" "github.com/pterm/pterm" ) -func printResponse(response *http.Response) string { - prefix := sfmt.Sprintf("%d %s", response.StatusCode, response.Request.Method) - printer := getPrefixPrinter(response.StatusCode, prefix) +func printResponse(request *contracts.Request, statusCode int) string { + prefix := sfmt.Sprintf("%d %s", statusCode, request.Method) + printer := getPrefixPrinter(statusCode, prefix) - return printer.Sprint(response.Request.URL.String()) + return printer.Sprint(request.URL.String()) } func getPrefixPrinter(statusCode int, text string) pterm.PrefixPrinter { - if statusCode < 100 || statusCode > 599 { - panic(sfmt.Sprintf("status code %d is not supported", statusCode)) - } - - if 100 <= statusCode && statusCode <= 199 { + if helpers.Is1xxCode(statusCode) { return pterm.PrefixPrinter{ MessageStyle: &pterm.ThemeDefault.InfoMessageStyle, Prefix: pterm.Prefix{ @@ -29,7 +25,7 @@ func getPrefixPrinter(statusCode int, text string) pterm.PrefixPrinter { } } - if 200 <= statusCode && statusCode <= 299 { + if helpers.Is2xxCode(statusCode) { return pterm.PrefixPrinter{ MessageStyle: &pterm.ThemeDefault.SuccessMessageStyle, Prefix: pterm.Prefix{ @@ -39,7 +35,7 @@ func getPrefixPrinter(statusCode int, text string) pterm.PrefixPrinter { } } - if 300 <= statusCode && statusCode <= 399 { + if helpers.Is3xxCode(statusCode) { return pterm.PrefixPrinter{ MessageStyle: &pterm.ThemeDefault.WarningMessageStyle, Prefix: pterm.Prefix{ @@ -49,11 +45,15 @@ func getPrefixPrinter(statusCode int, text string) pterm.PrefixPrinter { } } - return pterm.PrefixPrinter{ - MessageStyle: &pterm.ThemeDefault.ErrorMessageStyle, - Prefix: pterm.Prefix{ - Style: &pterm.ThemeDefault.ErrorPrefixStyle, - Text: text, - }, + if helpers.Is4xxCode(statusCode) || helpers.Is5xxCode(statusCode) { + return pterm.PrefixPrinter{ + MessageStyle: &pterm.ThemeDefault.ErrorMessageStyle, + Prefix: pterm.Prefix{ + Style: &pterm.ThemeDefault.ErrorPrefixStyle, + Text: text, + }, + } } + + panic(sfmt.Sprintf("status code %d is not supported", statusCode)) } diff --git a/internal/server/server.go b/internal/server/server.go index 5000091a..73f9b037 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -7,6 +7,7 @@ import ( "net/http" "time" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/helpers" "github.com/evg4b/uncors/internal/log" "golang.org/x/net/context" @@ -22,7 +23,7 @@ const ( shutdownTimeout = 15 * time.Second ) -func NewUncorsServer(ctx context.Context, handler http.Handler) *UncorsServer { +func NewUncorsServer(ctx context.Context, handler contracts.Handler) *UncorsServer { globalCtx, globalCtxCancel := context.WithCancel(ctx) server := &http.Server{ BaseContext: func(listener net.Listener) context.Context { @@ -31,7 +32,7 @@ func NewUncorsServer(ctx context.Context, handler http.Handler) *UncorsServer { ReadHeaderTimeout: readHeaderTimeout, Handler: http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { helpers.NormaliseRequest(request) - handler.ServeHTTP(writer, request) + handler.ServeHTTP(contracts.WrapResponseWriter(writer), request) }), ErrorLog: log.StandardErrorLogAdapter(), } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index befe0808..21f78aac 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/helpers" "github.com/evg4b/uncors/internal/server" "github.com/evg4b/uncors/internal/sfmt" @@ -19,7 +20,7 @@ func TestNewUncorsServer(t *testing.T) { ctx := context.Background() expectedResponse := "UNCORS OK!" - var handler http.HandlerFunc = func(w http.ResponseWriter, _r *http.Request) { + var handler contracts.HandlerFunc = func(w contracts.ResponseWriter, _ *contracts.Request) { w.WriteHeader(http.StatusOK) sfmt.Fprint(w, expectedResponse) } diff --git a/internal/ui/loggers.go b/internal/ui/loggers.go index 49556dbd..858cea86 100644 --- a/internal/ui/loggers.go +++ b/internal/ui/loggers.go @@ -10,7 +10,8 @@ func style(fg pterm.Color, bg pterm.Color) log.LoggerOption { } var ( - ProxyLogger = log.NewLogger(" PROXY ", style(pterm.FgBlack, pterm.BgLightBlue)) - MockLogger = log.NewLogger(" MOCK ", style(pterm.FgBlack, pterm.BgLightMagenta)) - StaticLogger = log.NewLogger("STATIC ", style(pterm.FgBlack, pterm.BgLightWhite)) + ProxyLogger = log.NewLogger(" PROXY ", style(pterm.FgBlack, pterm.BgLightBlue)) + MockLogger = log.NewLogger(" MOCK ", style(pterm.FgBlack, pterm.BgLightMagenta)) + StaticLogger = log.NewLogger(" STATIC ", style(pterm.FgBlack, pterm.BgLightWhite)) + CacheLogger = log.NewLogger(" CACHE ", style(pterm.FgBlack, pterm.BgLightYellow)) ) diff --git a/internal/urlreplacer/factory.go b/internal/urlreplacer/factory.go index 49a389d2..92d67c55 100644 --- a/internal/urlreplacer/factory.go +++ b/internal/urlreplacer/factory.go @@ -8,6 +8,10 @@ import ( "github.com/evg4b/uncors/internal/config" ) +type ReplacerFactory interface { + Make(requestURL *url.URL) (*Replacer, *Replacer, error) +} + type mapping struct { rawSource string source *Replacer diff --git a/main.go b/main.go index 4d3c4413..6ced9841 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,9 @@ import ( "strconv" "github.com/evg4b/uncors/internal/config" + "github.com/evg4b/uncors/internal/contracts" "github.com/evg4b/uncors/internal/handler" + "github.com/evg4b/uncors/internal/handler/cache" "github.com/evg4b/uncors/internal/infra" "github.com/evg4b/uncors/internal/log" "github.com/evg4b/uncors/internal/server" @@ -16,6 +18,7 @@ import ( "github.com/evg4b/uncors/internal/ui" "github.com/evg4b/uncors/internal/urlreplacer" "github.com/evg4b/uncors/internal/version" + goCache "github.com/patrickmn/go-cache" "github.com/pseidemann/finish" "github.com/spf13/afero" "github.com/spf13/pflag" @@ -73,9 +76,8 @@ func main() { panic(err) } - finisher := finish.Finisher{Log: infra.NoopLogger{}} - - ctx := context.Background() + cacheConfig := uncorsConfig.CacheConfig + cacheStorage := goCache.New(cacheConfig.ExpirationTime, cacheConfig.ClearTime) globalHandler := handler.NewUncorsRequestHandler( handler.WithMappings(mappings), @@ -83,8 +85,21 @@ func main() { handler.WithFileSystem(afero.NewOsFs()), handler.WithURLReplacerFactory(factory), handler.WithHTTPClient(httpClient), + handler.WithCacheMiddlewareFactory(func(key string, globs config.CacheGlobs) contracts.MiddlewareHandler { + return cache.NewMiddleware( + cache.WithLogger(ui.CacheLogger), + cache.WithPrefix(key), + cache.WithMethods(cacheConfig.Methods), + cache.WithCacheStorage(cacheStorage), + cache.WithGlobs(globs), + ) + }), ) + finisher := finish.Finisher{Log: infra.NoopLogger{}} + + ctx := context.Background() + uncorsServer := server.NewUncorsServer(ctx, globalHandler) log.Print(ui.Logo(Version)) diff --git a/testing/mocks/handler.go b/testing/mocks/handler.go new file mode 100644 index 00000000..31be8feb --- /dev/null +++ b/testing/mocks/handler.go @@ -0,0 +1,13 @@ +package mocks + +import ( + "testing" + + "github.com/evg4b/uncors/internal/contracts" +) + +func FailNowMock(t *testing.T) contracts.Handler { + return contracts.HandlerFunc(func(_ contracts.ResponseWriter, _ *contracts.Request) { + t.Fatal("should not be called") + }) +} diff --git a/testing/mocks/http_client_mock.go b/testing/mocks/http_client_mock.go index d079caba..518a6deb 100644 --- a/testing/mocks/http_client_mock.go +++ b/testing/mocks/http_client_mock.go @@ -9,6 +9,7 @@ import ( "sync" mm_atomic "sync/atomic" mm_time "time" + "github.com/gojuno/minimock/v3" ) diff --git a/testing/mocks/logger_mock.go b/testing/mocks/logger_mock.go index bd6554dc..d585ce70 100644 --- a/testing/mocks/logger_mock.go +++ b/testing/mocks/logger_mock.go @@ -5,10 +5,11 @@ package mocks //go:generate minimock -i github.com/evg4b/uncors/internal/contracts.Logger -o ./logger_mock.go -n LoggerMock import ( - "net/http" "sync" mm_atomic "sync/atomic" mm_time "time" + + mm_contracts "github.com/evg4b/uncors/internal/contracts" "github.com/gojuno/minimock/v3" ) @@ -52,8 +53,8 @@ type LoggerMock struct { beforeInfofCounter uint64 InfofMock mLoggerMockInfof - funcPrintResponse func(response *http.Response) - inspectFuncPrintResponse func(response *http.Response) + funcPrintResponse func(request *mm_contracts.Request, code int) + inspectFuncPrintResponse func(request *mm_contracts.Request, code int) afterPrintResponseCounter uint64 beforePrintResponseCounter uint64 PrintResponseMock mLoggerMockPrintResponse @@ -1252,11 +1253,12 @@ type LoggerMockPrintResponseExpectation struct { // LoggerMockPrintResponseParams contains parameters of the Logger.PrintResponse type LoggerMockPrintResponseParams struct { - response *http.Response + request *mm_contracts.Request + code int } // Expect sets up expected params for Logger.PrintResponse -func (mmPrintResponse *mLoggerMockPrintResponse) Expect(response *http.Response) *mLoggerMockPrintResponse { +func (mmPrintResponse *mLoggerMockPrintResponse) Expect(request *mm_contracts.Request, code int) *mLoggerMockPrintResponse { if mmPrintResponse.mock.funcPrintResponse != nil { mmPrintResponse.mock.t.Fatalf("LoggerMock.PrintResponse mock is already set by Set") } @@ -1265,7 +1267,7 @@ func (mmPrintResponse *mLoggerMockPrintResponse) Expect(response *http.Response) mmPrintResponse.defaultExpectation = &LoggerMockPrintResponseExpectation{} } - mmPrintResponse.defaultExpectation.params = &LoggerMockPrintResponseParams{response} + mmPrintResponse.defaultExpectation.params = &LoggerMockPrintResponseParams{request, code} for _, e := range mmPrintResponse.expectations { if minimock.Equal(e.params, mmPrintResponse.defaultExpectation.params) { mmPrintResponse.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmPrintResponse.defaultExpectation.params) @@ -1276,7 +1278,7 @@ func (mmPrintResponse *mLoggerMockPrintResponse) Expect(response *http.Response) } // Inspect accepts an inspector function that has same arguments as the Logger.PrintResponse -func (mmPrintResponse *mLoggerMockPrintResponse) Inspect(f func(response *http.Response)) *mLoggerMockPrintResponse { +func (mmPrintResponse *mLoggerMockPrintResponse) Inspect(f func(request *mm_contracts.Request, code int)) *mLoggerMockPrintResponse { if mmPrintResponse.mock.inspectFuncPrintResponse != nil { mmPrintResponse.mock.t.Fatalf("Inspect function is already set for LoggerMock.PrintResponse") } @@ -1300,7 +1302,7 @@ func (mmPrintResponse *mLoggerMockPrintResponse) Return() *LoggerMock { } //Set uses given function f to mock the Logger.PrintResponse method -func (mmPrintResponse *mLoggerMockPrintResponse) Set(f func(response *http.Response)) *LoggerMock { +func (mmPrintResponse *mLoggerMockPrintResponse) Set(f func(request *mm_contracts.Request, code int)) *LoggerMock { if mmPrintResponse.defaultExpectation != nil { mmPrintResponse.mock.t.Fatalf("Default expectation is already set for the Logger.PrintResponse method") } @@ -1314,15 +1316,15 @@ func (mmPrintResponse *mLoggerMockPrintResponse) Set(f func(response *http.Respo } // PrintResponse implements contracts.Logger -func (mmPrintResponse *LoggerMock) PrintResponse(response *http.Response) { +func (mmPrintResponse *LoggerMock) PrintResponse(request *mm_contracts.Request, code int) { mm_atomic.AddUint64(&mmPrintResponse.beforePrintResponseCounter, 1) defer mm_atomic.AddUint64(&mmPrintResponse.afterPrintResponseCounter, 1) if mmPrintResponse.inspectFuncPrintResponse != nil { - mmPrintResponse.inspectFuncPrintResponse(response) + mmPrintResponse.inspectFuncPrintResponse(request, code) } - mm_params := &LoggerMockPrintResponseParams{response} + mm_params := &LoggerMockPrintResponseParams{request, code} // Record call args mmPrintResponse.PrintResponseMock.mutex.Lock() @@ -1339,7 +1341,7 @@ func (mmPrintResponse *LoggerMock) PrintResponse(response *http.Response) { if mmPrintResponse.PrintResponseMock.defaultExpectation != nil { mm_atomic.AddUint64(&mmPrintResponse.PrintResponseMock.defaultExpectation.Counter, 1) mm_want := mmPrintResponse.PrintResponseMock.defaultExpectation.params - mm_got := LoggerMockPrintResponseParams{response} + mm_got := LoggerMockPrintResponseParams{request, code} if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { mmPrintResponse.t.Errorf("LoggerMock.PrintResponse got unexpected parameters, want: %#v, got: %#v%s\n", *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) } @@ -1348,10 +1350,10 @@ func (mmPrintResponse *LoggerMock) PrintResponse(response *http.Response) { } if mmPrintResponse.funcPrintResponse != nil { - mmPrintResponse.funcPrintResponse(response) + mmPrintResponse.funcPrintResponse(request, code) return } - mmPrintResponse.t.Fatalf("Unexpected call to LoggerMock.PrintResponse. %v", response) + mmPrintResponse.t.Fatalf("Unexpected call to LoggerMock.PrintResponse. %v %v", request, code) } diff --git a/testing/mocks/replacer_factory_mock.go b/testing/mocks/replacer_factory_mock.go new file mode 100644 index 00000000..9d64b1c6 --- /dev/null +++ b/testing/mocks/replacer_factory_mock.go @@ -0,0 +1,286 @@ +package mocks + +// Code generated by http://github.com/gojuno/minimock (dev). DO NOT EDIT. + +//go:generate minimock -i github.com/evg4b/uncors/internal/urlreplacer.ReplacerFactory -o ./replacer_factory_mock.go -n ReplacerFactoryMock + +import ( + "net/url" + "sync" + mm_atomic "sync/atomic" + mm_time "time" + + mm_urlreplacer "github.com/evg4b/uncors/internal/urlreplacer" + "github.com/gojuno/minimock/v3" +) + +// ReplacerFactoryMock implements urlreplacer.ReplacerFactory +type ReplacerFactoryMock struct { + t minimock.Tester + + funcMake func(requestURL *url.URL) (rp1 *mm_urlreplacer.Replacer, rp2 *mm_urlreplacer.Replacer, err error) + inspectFuncMake func(requestURL *url.URL) + afterMakeCounter uint64 + beforeMakeCounter uint64 + MakeMock mReplacerFactoryMockMake +} + +// NewReplacerFactoryMock returns a mock for urlreplacer.ReplacerFactory +func NewReplacerFactoryMock(t minimock.Tester) *ReplacerFactoryMock { + m := &ReplacerFactoryMock{t: t} + if controller, ok := t.(minimock.MockController); ok { + controller.RegisterMocker(m) + } + + m.MakeMock = mReplacerFactoryMockMake{mock: m} + m.MakeMock.callArgs = []*ReplacerFactoryMockMakeParams{} + + return m +} + +type mReplacerFactoryMockMake struct { + mock *ReplacerFactoryMock + defaultExpectation *ReplacerFactoryMockMakeExpectation + expectations []*ReplacerFactoryMockMakeExpectation + + callArgs []*ReplacerFactoryMockMakeParams + mutex sync.RWMutex +} + +// ReplacerFactoryMockMakeExpectation specifies expectation struct of the ReplacerFactory.Make +type ReplacerFactoryMockMakeExpectation struct { + mock *ReplacerFactoryMock + params *ReplacerFactoryMockMakeParams + results *ReplacerFactoryMockMakeResults + Counter uint64 +} + +// ReplacerFactoryMockMakeParams contains parameters of the ReplacerFactory.Make +type ReplacerFactoryMockMakeParams struct { + requestURL *url.URL +} + +// ReplacerFactoryMockMakeResults contains results of the ReplacerFactory.Make +type ReplacerFactoryMockMakeResults struct { + rp1 *mm_urlreplacer.Replacer + rp2 *mm_urlreplacer.Replacer + err error +} + +// Expect sets up expected params for ReplacerFactory.Make +func (mmMake *mReplacerFactoryMockMake) Expect(requestURL *url.URL) *mReplacerFactoryMockMake { + if mmMake.mock.funcMake != nil { + mmMake.mock.t.Fatalf("ReplacerFactoryMock.Make mock is already set by Set") + } + + if mmMake.defaultExpectation == nil { + mmMake.defaultExpectation = &ReplacerFactoryMockMakeExpectation{} + } + + mmMake.defaultExpectation.params = &ReplacerFactoryMockMakeParams{requestURL} + for _, e := range mmMake.expectations { + if minimock.Equal(e.params, mmMake.defaultExpectation.params) { + mmMake.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmMake.defaultExpectation.params) + } + } + + return mmMake +} + +// Inspect accepts an inspector function that has same arguments as the ReplacerFactory.Make +func (mmMake *mReplacerFactoryMockMake) Inspect(f func(requestURL *url.URL)) *mReplacerFactoryMockMake { + if mmMake.mock.inspectFuncMake != nil { + mmMake.mock.t.Fatalf("Inspect function is already set for ReplacerFactoryMock.Make") + } + + mmMake.mock.inspectFuncMake = f + + return mmMake +} + +// Return sets up results that will be returned by ReplacerFactory.Make +func (mmMake *mReplacerFactoryMockMake) Return(rp1 *mm_urlreplacer.Replacer, rp2 *mm_urlreplacer.Replacer, err error) *ReplacerFactoryMock { + if mmMake.mock.funcMake != nil { + mmMake.mock.t.Fatalf("ReplacerFactoryMock.Make mock is already set by Set") + } + + if mmMake.defaultExpectation == nil { + mmMake.defaultExpectation = &ReplacerFactoryMockMakeExpectation{mock: mmMake.mock} + } + mmMake.defaultExpectation.results = &ReplacerFactoryMockMakeResults{rp1, rp2, err} + return mmMake.mock +} + +//Set uses given function f to mock the ReplacerFactory.Make method +func (mmMake *mReplacerFactoryMockMake) Set(f func(requestURL *url.URL) (rp1 *mm_urlreplacer.Replacer, rp2 *mm_urlreplacer.Replacer, err error)) *ReplacerFactoryMock { + if mmMake.defaultExpectation != nil { + mmMake.mock.t.Fatalf("Default expectation is already set for the ReplacerFactory.Make method") + } + + if len(mmMake.expectations) > 0 { + mmMake.mock.t.Fatalf("Some expectations are already set for the ReplacerFactory.Make method") + } + + mmMake.mock.funcMake = f + return mmMake.mock +} + +// When sets expectation for the ReplacerFactory.Make which will trigger the result defined by the following +// Then helper +func (mmMake *mReplacerFactoryMockMake) When(requestURL *url.URL) *ReplacerFactoryMockMakeExpectation { + if mmMake.mock.funcMake != nil { + mmMake.mock.t.Fatalf("ReplacerFactoryMock.Make mock is already set by Set") + } + + expectation := &ReplacerFactoryMockMakeExpectation{ + mock: mmMake.mock, + params: &ReplacerFactoryMockMakeParams{requestURL}, + } + mmMake.expectations = append(mmMake.expectations, expectation) + return expectation +} + +// Then sets up ReplacerFactory.Make return parameters for the expectation previously defined by the When method +func (e *ReplacerFactoryMockMakeExpectation) Then(rp1 *mm_urlreplacer.Replacer, rp2 *mm_urlreplacer.Replacer, err error) *ReplacerFactoryMock { + e.results = &ReplacerFactoryMockMakeResults{rp1, rp2, err} + return e.mock +} + +// Make implements urlreplacer.ReplacerFactory +func (mmMake *ReplacerFactoryMock) Make(requestURL *url.URL) (rp1 *mm_urlreplacer.Replacer, rp2 *mm_urlreplacer.Replacer, err error) { + mm_atomic.AddUint64(&mmMake.beforeMakeCounter, 1) + defer mm_atomic.AddUint64(&mmMake.afterMakeCounter, 1) + + if mmMake.inspectFuncMake != nil { + mmMake.inspectFuncMake(requestURL) + } + + mm_params := &ReplacerFactoryMockMakeParams{requestURL} + + // Record call args + mmMake.MakeMock.mutex.Lock() + mmMake.MakeMock.callArgs = append(mmMake.MakeMock.callArgs, mm_params) + mmMake.MakeMock.mutex.Unlock() + + for _, e := range mmMake.MakeMock.expectations { + if minimock.Equal(e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.rp1, e.results.rp2, e.results.err + } + } + + if mmMake.MakeMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmMake.MakeMock.defaultExpectation.Counter, 1) + mm_want := mmMake.MakeMock.defaultExpectation.params + mm_got := ReplacerFactoryMockMakeParams{requestURL} + if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmMake.t.Errorf("ReplacerFactoryMock.Make got unexpected parameters, want: %#v, got: %#v%s\n", *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmMake.MakeMock.defaultExpectation.results + if mm_results == nil { + mmMake.t.Fatal("No results are set for the ReplacerFactoryMock.Make") + } + return (*mm_results).rp1, (*mm_results).rp2, (*mm_results).err + } + if mmMake.funcMake != nil { + return mmMake.funcMake(requestURL) + } + mmMake.t.Fatalf("Unexpected call to ReplacerFactoryMock.Make. %v", requestURL) + return +} + +// MakeAfterCounter returns a count of finished ReplacerFactoryMock.Make invocations +func (mmMake *ReplacerFactoryMock) MakeAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmMake.afterMakeCounter) +} + +// MakeBeforeCounter returns a count of ReplacerFactoryMock.Make invocations +func (mmMake *ReplacerFactoryMock) MakeBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmMake.beforeMakeCounter) +} + +// Calls returns a list of arguments used in each call to ReplacerFactoryMock.Make. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmMake *mReplacerFactoryMockMake) Calls() []*ReplacerFactoryMockMakeParams { + mmMake.mutex.RLock() + + argCopy := make([]*ReplacerFactoryMockMakeParams, len(mmMake.callArgs)) + copy(argCopy, mmMake.callArgs) + + mmMake.mutex.RUnlock() + + return argCopy +} + +// MinimockMakeDone returns true if the count of the Make invocations corresponds +// the number of defined expectations +func (m *ReplacerFactoryMock) MinimockMakeDone() bool { + for _, e := range m.MakeMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + // if default expectation was set then invocations count should be greater than zero + if m.MakeMock.defaultExpectation != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { + return false + } + // if func was set then invocations count should be greater than zero + if m.funcMake != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { + return false + } + return true +} + +// MinimockMakeInspect logs each unmet expectation +func (m *ReplacerFactoryMock) MinimockMakeInspect() { + for _, e := range m.MakeMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to ReplacerFactoryMock.Make with params: %#v", *e.params) + } + } + + // if default expectation was set then invocations count should be greater than zero + if m.MakeMock.defaultExpectation != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { + if m.MakeMock.defaultExpectation.params == nil { + m.t.Error("Expected call to ReplacerFactoryMock.Make") + } else { + m.t.Errorf("Expected call to ReplacerFactoryMock.Make with params: %#v", *m.MakeMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcMake != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { + m.t.Error("Expected call to ReplacerFactoryMock.Make") + } +} + +// MinimockFinish checks that all mocked methods have been called the expected number of times +func (m *ReplacerFactoryMock) MinimockFinish() { + if !m.minimockDone() { + m.MinimockMakeInspect() + m.t.FailNow() + } +} + +// MinimockWait waits for all mocked methods to be called the expected number of times +func (m *ReplacerFactoryMock) MinimockWait(timeout mm_time.Duration) { + timeoutCh := mm_time.After(timeout) + for { + if m.minimockDone() { + return + } + select { + case <-timeoutCh: + m.MinimockFinish() + return + case <-mm_time.After(10 * mm_time.Millisecond): + } + } +} + +func (m *ReplacerFactoryMock) minimockDone() bool { + done := true + return done && + m.MinimockMakeDone() +} diff --git a/testing/mocks/urlreplacer_factory_mock.go b/testing/mocks/urlreplacer_factory_mock.go deleted file mode 100644 index 5f8fdaf0..00000000 --- a/testing/mocks/urlreplacer_factory_mock.go +++ /dev/null @@ -1,285 +0,0 @@ -package mocks - -// Code generated by http://github.com/gojuno/minimock (dev). DO NOT EDIT. - -//go:generate minimock -i github.com/evg4b/uncors/internal/middlewares/proxy.URLReplacerFactory -o ./urlreplacer_factory_mock.go -n URLReplacerFactoryMock - -import ( - "net/url" - "sync" - mm_atomic "sync/atomic" - mm_time "time" - "github.com/evg4b/uncors/internal/urlreplacer" - "github.com/gojuno/minimock/v3" -) - -// URLReplacerFactoryMock implements proxy.URLReplacerFactory -type URLReplacerFactoryMock struct { - t minimock.Tester - - funcMake func(requestURL *url.URL) (rp1 *urlreplacer.Replacer, rp2 *urlreplacer.Replacer, err error) - inspectFuncMake func(requestURL *url.URL) - afterMakeCounter uint64 - beforeMakeCounter uint64 - MakeMock mURLReplacerFactoryMockMake -} - -// NewURLReplacerFactoryMock returns a mock for proxy.URLReplacerFactory -func NewURLReplacerFactoryMock(t minimock.Tester) *URLReplacerFactoryMock { - m := &URLReplacerFactoryMock{t: t} - if controller, ok := t.(minimock.MockController); ok { - controller.RegisterMocker(m) - } - - m.MakeMock = mURLReplacerFactoryMockMake{mock: m} - m.MakeMock.callArgs = []*URLReplacerFactoryMockMakeParams{} - - return m -} - -type mURLReplacerFactoryMockMake struct { - mock *URLReplacerFactoryMock - defaultExpectation *URLReplacerFactoryMockMakeExpectation - expectations []*URLReplacerFactoryMockMakeExpectation - - callArgs []*URLReplacerFactoryMockMakeParams - mutex sync.RWMutex -} - -// URLReplacerFactoryMockMakeExpectation specifies expectation struct of the URLReplacerFactory.Make -type URLReplacerFactoryMockMakeExpectation struct { - mock *URLReplacerFactoryMock - params *URLReplacerFactoryMockMakeParams - results *URLReplacerFactoryMockMakeResults - Counter uint64 -} - -// URLReplacerFactoryMockMakeParams contains parameters of the URLReplacerFactory.Make -type URLReplacerFactoryMockMakeParams struct { - requestURL *url.URL -} - -// URLReplacerFactoryMockMakeResults contains results of the URLReplacerFactory.Make -type URLReplacerFactoryMockMakeResults struct { - rp1 *urlreplacer.Replacer - rp2 *urlreplacer.Replacer - err error -} - -// Expect sets up expected params for URLReplacerFactory.Make -func (mmMake *mURLReplacerFactoryMockMake) Expect(requestURL *url.URL) *mURLReplacerFactoryMockMake { - if mmMake.mock.funcMake != nil { - mmMake.mock.t.Fatalf("URLReplacerFactoryMock.Make mock is already set by Set") - } - - if mmMake.defaultExpectation == nil { - mmMake.defaultExpectation = &URLReplacerFactoryMockMakeExpectation{} - } - - mmMake.defaultExpectation.params = &URLReplacerFactoryMockMakeParams{requestURL} - for _, e := range mmMake.expectations { - if minimock.Equal(e.params, mmMake.defaultExpectation.params) { - mmMake.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmMake.defaultExpectation.params) - } - } - - return mmMake -} - -// Inspect accepts an inspector function that has same arguments as the URLReplacerFactory.Make -func (mmMake *mURLReplacerFactoryMockMake) Inspect(f func(requestURL *url.URL)) *mURLReplacerFactoryMockMake { - if mmMake.mock.inspectFuncMake != nil { - mmMake.mock.t.Fatalf("Inspect function is already set for URLReplacerFactoryMock.Make") - } - - mmMake.mock.inspectFuncMake = f - - return mmMake -} - -// Return sets up results that will be returned by URLReplacerFactory.Make -func (mmMake *mURLReplacerFactoryMockMake) Return(rp1 *urlreplacer.Replacer, rp2 *urlreplacer.Replacer, err error) *URLReplacerFactoryMock { - if mmMake.mock.funcMake != nil { - mmMake.mock.t.Fatalf("URLReplacerFactoryMock.Make mock is already set by Set") - } - - if mmMake.defaultExpectation == nil { - mmMake.defaultExpectation = &URLReplacerFactoryMockMakeExpectation{mock: mmMake.mock} - } - mmMake.defaultExpectation.results = &URLReplacerFactoryMockMakeResults{rp1, rp2, err} - return mmMake.mock -} - -//Set uses given function f to mock the URLReplacerFactory.Make method -func (mmMake *mURLReplacerFactoryMockMake) Set(f func(requestURL *url.URL) (rp1 *urlreplacer.Replacer, rp2 *urlreplacer.Replacer, err error)) *URLReplacerFactoryMock { - if mmMake.defaultExpectation != nil { - mmMake.mock.t.Fatalf("Default expectation is already set for the URLReplacerFactory.Make method") - } - - if len(mmMake.expectations) > 0 { - mmMake.mock.t.Fatalf("Some expectations are already set for the URLReplacerFactory.Make method") - } - - mmMake.mock.funcMake = f - return mmMake.mock -} - -// When sets expectation for the URLReplacerFactory.Make which will trigger the result defined by the following -// Then helper -func (mmMake *mURLReplacerFactoryMockMake) When(requestURL *url.URL) *URLReplacerFactoryMockMakeExpectation { - if mmMake.mock.funcMake != nil { - mmMake.mock.t.Fatalf("URLReplacerFactoryMock.Make mock is already set by Set") - } - - expectation := &URLReplacerFactoryMockMakeExpectation{ - mock: mmMake.mock, - params: &URLReplacerFactoryMockMakeParams{requestURL}, - } - mmMake.expectations = append(mmMake.expectations, expectation) - return expectation -} - -// Then sets up URLReplacerFactory.Make return parameters for the expectation previously defined by the When method -func (e *URLReplacerFactoryMockMakeExpectation) Then(rp1 *urlreplacer.Replacer, rp2 *urlreplacer.Replacer, err error) *URLReplacerFactoryMock { - e.results = &URLReplacerFactoryMockMakeResults{rp1, rp2, err} - return e.mock -} - -// Make implements proxy.URLReplacerFactory -func (mmMake *URLReplacerFactoryMock) Make(requestURL *url.URL) (rp1 *urlreplacer.Replacer, rp2 *urlreplacer.Replacer, err error) { - mm_atomic.AddUint64(&mmMake.beforeMakeCounter, 1) - defer mm_atomic.AddUint64(&mmMake.afterMakeCounter, 1) - - if mmMake.inspectFuncMake != nil { - mmMake.inspectFuncMake(requestURL) - } - - mm_params := &URLReplacerFactoryMockMakeParams{requestURL} - - // Record call args - mmMake.MakeMock.mutex.Lock() - mmMake.MakeMock.callArgs = append(mmMake.MakeMock.callArgs, mm_params) - mmMake.MakeMock.mutex.Unlock() - - for _, e := range mmMake.MakeMock.expectations { - if minimock.Equal(e.params, mm_params) { - mm_atomic.AddUint64(&e.Counter, 1) - return e.results.rp1, e.results.rp2, e.results.err - } - } - - if mmMake.MakeMock.defaultExpectation != nil { - mm_atomic.AddUint64(&mmMake.MakeMock.defaultExpectation.Counter, 1) - mm_want := mmMake.MakeMock.defaultExpectation.params - mm_got := URLReplacerFactoryMockMakeParams{requestURL} - if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { - mmMake.t.Errorf("URLReplacerFactoryMock.Make got unexpected parameters, want: %#v, got: %#v%s\n", *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) - } - - mm_results := mmMake.MakeMock.defaultExpectation.results - if mm_results == nil { - mmMake.t.Fatal("No results are set for the URLReplacerFactoryMock.Make") - } - return (*mm_results).rp1, (*mm_results).rp2, (*mm_results).err - } - if mmMake.funcMake != nil { - return mmMake.funcMake(requestURL) - } - mmMake.t.Fatalf("Unexpected call to URLReplacerFactoryMock.Make. %v", requestURL) - return -} - -// MakeAfterCounter returns a count of finished URLReplacerFactoryMock.Make invocations -func (mmMake *URLReplacerFactoryMock) MakeAfterCounter() uint64 { - return mm_atomic.LoadUint64(&mmMake.afterMakeCounter) -} - -// MakeBeforeCounter returns a count of URLReplacerFactoryMock.Make invocations -func (mmMake *URLReplacerFactoryMock) MakeBeforeCounter() uint64 { - return mm_atomic.LoadUint64(&mmMake.beforeMakeCounter) -} - -// Calls returns a list of arguments used in each call to URLReplacerFactoryMock.Make. -// The list is in the same order as the calls were made (i.e. recent calls have a higher index) -func (mmMake *mURLReplacerFactoryMockMake) Calls() []*URLReplacerFactoryMockMakeParams { - mmMake.mutex.RLock() - - argCopy := make([]*URLReplacerFactoryMockMakeParams, len(mmMake.callArgs)) - copy(argCopy, mmMake.callArgs) - - mmMake.mutex.RUnlock() - - return argCopy -} - -// MinimockMakeDone returns true if the count of the Make invocations corresponds -// the number of defined expectations -func (m *URLReplacerFactoryMock) MinimockMakeDone() bool { - for _, e := range m.MakeMock.expectations { - if mm_atomic.LoadUint64(&e.Counter) < 1 { - return false - } - } - - // if default expectation was set then invocations count should be greater than zero - if m.MakeMock.defaultExpectation != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { - return false - } - // if func was set then invocations count should be greater than zero - if m.funcMake != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { - return false - } - return true -} - -// MinimockMakeInspect logs each unmet expectation -func (m *URLReplacerFactoryMock) MinimockMakeInspect() { - for _, e := range m.MakeMock.expectations { - if mm_atomic.LoadUint64(&e.Counter) < 1 { - m.t.Errorf("Expected call to URLReplacerFactoryMock.Make with params: %#v", *e.params) - } - } - - // if default expectation was set then invocations count should be greater than zero - if m.MakeMock.defaultExpectation != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { - if m.MakeMock.defaultExpectation.params == nil { - m.t.Error("Expected call to URLReplacerFactoryMock.Make") - } else { - m.t.Errorf("Expected call to URLReplacerFactoryMock.Make with params: %#v", *m.MakeMock.defaultExpectation.params) - } - } - // if func was set then invocations count should be greater than zero - if m.funcMake != nil && mm_atomic.LoadUint64(&m.afterMakeCounter) < 1 { - m.t.Error("Expected call to URLReplacerFactoryMock.Make") - } -} - -// MinimockFinish checks that all mocked methods have been called the expected number of times -func (m *URLReplacerFactoryMock) MinimockFinish() { - if !m.minimockDone() { - m.MinimockMakeInspect() - m.t.FailNow() - } -} - -// MinimockWait waits for all mocked methods to be called the expected number of times -func (m *URLReplacerFactoryMock) MinimockWait(timeout mm_time.Duration) { - timeoutCh := mm_time.After(timeout) - for { - if m.minimockDone() { - return - } - select { - case <-timeoutCh: - m.MinimockFinish() - return - case <-mm_time.After(10 * mm_time.Millisecond): - } - } -} - -func (m *URLReplacerFactoryMock) minimockDone() bool { - done := true - return done && - m.MinimockMakeDone() -} diff --git a/testing/testutils/func.go b/testing/testutils/func.go new file mode 100644 index 00000000..e6d8eab8 --- /dev/null +++ b/testing/testutils/func.go @@ -0,0 +1,7 @@ +package testutils + +func Times(n int, function func()) { + for i := 0; i < n; i++ { + function() + } +} diff --git a/testing/testutils/http.go b/testing/testutils/http.go new file mode 100644 index 00000000..b26aafa5 --- /dev/null +++ b/testing/testutils/http.go @@ -0,0 +1,35 @@ +package testutils + +import ( + "net/http" + + "github.com/evg4b/uncors/internal/contracts" +) + +type handlerFunc = func(writer contracts.ResponseWriter, request *contracts.Request) + +type CountableHandler struct { + handler handlerFunc + count int +} + +func NewCounter(handler handlerFunc) *CountableHandler { + return &CountableHandler{handler, 0} +} + +func (t *CountableHandler) ServeHTTP(writer contracts.ResponseWriter, request *contracts.Request) { + t.count++ + t.handler(writer, request) +} + +func (t *CountableHandler) Count() int { + return t.count +} + +func CopyHeaders(from http.Header, to http.Header) { + for key, values := range from { + for _, value := range values { + to.Add(key, value) + } + } +} diff --git a/testing/testutils/http_body..go b/testing/testutils/http_body..go deleted file mode 100644 index 76191242..00000000 --- a/testing/testutils/http_body..go +++ /dev/null @@ -1,31 +0,0 @@ -package testutils - -import ( - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/evg4b/uncors/internal/helpers" -) - -func ReadBody(t *testing.T, recorder *httptest.ResponseRecorder) string { - t.Helper() - - response := recorder.Result() - defer helpers.CloseSafe(response.Body) - - body, err := io.ReadAll(response.Body) - CheckNoError(t, err) - - return string(body) -} - -func ReadHeader(t *testing.T, recorder *httptest.ResponseRecorder) http.Header { - t.Helper() - - response := recorder.Result() - defer helpers.CloseSafe(response.Body) - - return response.Header -} diff --git a/testing/testutils/http_client.go b/testing/testutils/http_client.go index ed2eb8e6..e89cc0d0 100644 --- a/testing/testutils/http_client.go +++ b/testing/testutils/http_client.go @@ -1,7 +1,12 @@ package testutils import ( + "io" "net/http" + "net/http/httptest" + "testing" + + "github.com/evg4b/uncors/internal/helpers" ) type RoundTripFunc func(req *http.Request) *http.Response @@ -13,3 +18,24 @@ func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { func NewTestClient(responseCreator RoundTripFunc) *http.Client { return &http.Client{Transport: responseCreator} } + +func ReadBody(t *testing.T, recorder *httptest.ResponseRecorder) string { + t.Helper() + + response := recorder.Result() + defer helpers.CloseSafe(response.Body) + + body, err := io.ReadAll(response.Body) + CheckNoError(t, err) + + return string(body) +} + +func ReadHeader(t *testing.T, recorder *httptest.ResponseRecorder) http.Header { + t.Helper() + + response := recorder.Result() + defer helpers.CloseSafe(response.Body) + + return response.Header +}