diff --git a/.gitignore b/.gitignore index 199c085..bb1df83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +!/**/.gitkeep + /.idea .env /vendor +/test/data/* diff --git a/Makefile b/Makefile index 217808f..9e5ae85 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,23 @@ race: build: go build cmd/server/main.go -test: - go test cmd/server/main.go +tests: + go test -v -run=. test/**/*.go + +bench: bench-clean + go test -run=. -bench=. -benchtime=5s -count 5 -benchmem -cpuprofile test/data/cpu.out -memprofile test/data/mem.out -trace test/data/trace.out ./test/handler/create_category_test.go | tee test/data/bench.log + +bench-cpu: + go tool pprof -http :19800 test/data/cpu.out + +bench-mem: + go tool pprof -http :19801 test/data/mem.out + +bench-trace: + go tool trace test/data/trace.out + +bench-clean: + rm -f test/data/*.out test/data/bench.log # run-profiler: # go run cmd/server/main.go -cpuprofile cpu.prof -memprofile mem.prof diff --git a/go.mod b/go.mod index fa0d780..848ae78 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,14 @@ module git.pbiernat.dev/golang/rest-api-prototype go 1.17 require ( - github.com/georgysavva/scany v0.3.0 + github.com/go-ozzo/ozzo-validation v3.6.0+incompatible github.com/gorilla/mux v1.8.0 github.com/jackc/pgx/v4 v4.15.0 github.com/joho/godotenv v1.4.0 ) require ( + github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.11.0 // indirect github.com/jackc/pgio v1.0.0 // indirect diff --git a/go.sum b/go.sum index 9f92291..6b286c5 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,20 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= +github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs= -github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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= -github.com/georgysavva/scany v0.3.0 h1:MA1aEqPbnNuiek59gMpNPqQrXXroyFj5jCADlETdxiA= -github.com/georgysavva/scany v0.3.0/go.mod h1:q8QyrfXjmBk9iJD00igd4lbkAKEXAH/zIYoZ0z/Wan4= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible h1:msy24VGS42fKO9K1vLz82/GeYW1cILu7Nuuj1N3BBkE= +github.com/go-ozzo/ozzo-validation v3.6.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -32,9 +28,6 @@ github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgO github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= -github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= @@ -54,44 +47,29 @@ github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= -github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= -github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= -github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= -github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= -github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= -github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jmoiron/sqlx v1.3.1/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -104,19 +82,13 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -128,14 +100,12 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -159,12 +129,9 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -184,21 +151,17 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -213,17 +176,12 @@ golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= -gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= -gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/app/entity/category.go b/internal/app/entity/category.go index 124ce7e..e021298 100644 --- a/internal/app/entity/category.go +++ b/internal/app/entity/category.go @@ -5,8 +5,14 @@ import ( ) type Category struct { - ID int - Name string - CreateDate time.Time - ModifyDate time.Time + ID int `json:"id"` + Name string `json:"name"` + CreateDate time.Time `json:"create_date"` + ModifyDate time.Time `json:"modify_date"` // FIXME } + +// func (c Category) Validate() error { +// return validation.ValidateStruct(&c, +// validation.Field(&c.Name, validation.Required, validation.Length(3, 255)), +// ) +// } diff --git a/internal/app/handler/article.go b/internal/app/handler/article.go index fb8be22..b6e9414 100644 --- a/internal/app/handler/article.go +++ b/internal/app/handler/article.go @@ -1,6 +1,7 @@ package handler import ( + "log" "net/http" "time" @@ -30,19 +31,28 @@ type CreateArticleResponse struct { Err string `json:"err,omitempty"` } -func CreateArticleHandlerFunc(env *Env, req interface{}, w http.ResponseWriter) (interface{}, error) { - // var art = req.(*CreateArticleRequest) - // doSthWith(art) +func CreateArticleHandlerFunc(h *Handler, w http.ResponseWriter) (interface{}, int, error) { + var art = h.Request.(*CreateArticleRequest) + log.Println(art) - return &CreateArticleResponse{ - Status: http.StatusText(http.StatusOK), - Data: &entity.Article{ - ID: 1, - CategoryID: 1, - Title: "Dummy article", - Intro: "Intro", - Text: "Text", - CreateDate: time.Now(), - }, - }, nil + return &entity.Article{ + ID: 1, + CategoryID: 1, + Title: "Dummy article", + Intro: "Intro", + Text: "Text", + CreateDate: time.Now(), + }, http.StatusCreated, nil + + // return &CreateArticleResponse{ + // Status: http.StatusText(http.StatusOK), + // Data: &entity.Article{ + // ID: 1, + // CategoryID: 1, + // Title: "Dummy article", + // Intro: "Intro", + // Text: "Text", + // CreateDate: time.Now(), + // }, + // }, http.StatusCreated, nil } diff --git a/internal/app/handler/category.go b/internal/app/handler/category.go index f0519f5..8da9f1b 100644 --- a/internal/app/handler/category.go +++ b/internal/app/handler/category.go @@ -1,13 +1,17 @@ package handler import ( + "log" "net/http" + "strconv" "time" "git.pbiernat.dev/golang/rest-api-prototype/internal/app/entity" + validation "github.com/go-ozzo/ozzo-validation" ) var CreateCategoryHandler *Handler +var DeleteCategoryHandler *Handler func init() { CreateCategoryHandler = &Handler{ @@ -15,27 +19,67 @@ func init() { Request: &CreateCategoryRequest{}, Response: &CreateCategoryResponse{}, } + + DeleteCategoryHandler = &Handler{ + Handle: DeleteCategoryHandlerFunc, + Request: &DeleteCategoryRequest{}, + Response: &DeleteCategoryResponse{}, + } } type CreateCategoryRequest struct { Name string `json:"name"` } +func (c CreateCategoryRequest) Validate() error { + return validation.ValidateStruct(&c, + validation.Field(&c.Name, validation.Required, validation.Length(3, 255)), + ) +} + type CreateCategoryResponse struct { - Status string `json:"status"` - Data *entity.Category `json:"data"` - Err string `json:"err,omitempty"` + Data *entity.Category `json:"data"` + Err string `json:"err,omitempty"` // FIXME: omitempty on/off? } -func CreateCategoryHandlerFunc(env *Env, req interface{}, w http.ResponseWriter) (interface{}, error) { - // var cat = req.(*CreateCategoryRequest) - // doSthWith(cat) - - return &CreateCategoryResponse{ - Status: http.StatusText(http.StatusOK), - Data: &entity.Category{ - Name: "Dummy category", - CreateDate: time.Now(), - }, - }, nil +type DeleteCategoryRequest struct { +} + +type DeleteCategoryResponse struct { +} + +func CreateCategoryHandlerFunc(h *Handler, w http.ResponseWriter) (interface{}, int, error) { + var cat = h.Request.(*CreateCategoryRequest) + log.Println("Cat input:", cat) + + if err := cat.Validate(); err != nil { + log.Println("Create category validation errors:", err) + return nil, http.StatusUnprocessableEntity, err + } + + return &entity.Category{ + Name: cat.Name, + CreateDate: time.Now(), + }, http.StatusCreated, nil + + // return &CreateCategoryResponse{ + // Data: &entity.Category{ + // Name: "Dummy category", + // CreateDate: time.Now(), + // }, + // }, http.StatusCreated, nil +} + +func DeleteCategoryHandlerFunc(h *Handler, w http.ResponseWriter) (interface{}, int, error) { + var cat = h.Request.(*DeleteCategoryRequest) + log.Println(cat) + + id, _ := strconv.Atoi(h.Params["id"]) + log.Println(h.Params) + + if id != 1 { + return &DeleteCategoryResponse{}, http.StatusNotFound, nil + } + + return &DeleteCategoryResponse{}, http.StatusNoContent, nil } diff --git a/internal/app/handler/error.go b/internal/app/handler/error.go index 8b8d8bf..75291c7 100644 --- a/internal/app/handler/error.go +++ b/internal/app/handler/error.go @@ -3,19 +3,21 @@ package handler import "net/http" type ErrorResponse struct { - Message string `json:"message"` + Error string `json:"error"` +} + +func Error(err string) *ErrorResponse { + return &ErrorResponse{Error: err} } type NotFoundHandler struct{} func (NotFoundHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotFound) - encodeResponse(w, &ErrorResponse{Message: "Path " + r.RequestURI + " not found"}, nil) + encodeResponse(w, &response{http.StatusNotFound, "Path " + r.RequestURI + " not found"}, nil) } type MethodNotAllowedHandler struct{} func (MethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusMethodNotAllowed) - encodeResponse(w, &ErrorResponse{Message: "Method Not Allowed: " + r.Method}, nil) + encodeResponse(w, &response{http.StatusMethodNotAllowed, "Method Not Allowed: " + r.Method}, nil) } diff --git a/internal/app/handler/handler.go b/internal/app/handler/handler.go index 8a376f2..9330243 100644 --- a/internal/app/handler/handler.go +++ b/internal/app/handler/handler.go @@ -7,6 +7,7 @@ import ( "log" "net/http" + "github.com/gorilla/mux" "github.com/jackc/pgx/v4/pgxpool" ) @@ -15,27 +16,38 @@ type Env struct { DB *pgxpool.Pool } -type HandlerFunc func(e *Env, req interface{}, w http.ResponseWriter) (interface{}, error) - type Handler struct { *Env Handle HandlerFunc Request interface{} Response interface{} + Params Set } -func New(env *Env, h *Handler) *Handler { - return &Handler{env, h.Handle, h.Request, h.Response} +type HandlerFunc func(h *Handler, w http.ResponseWriter) (interface{}, int, error) + +type Set map[string]string + +type response struct { + Status int + Data interface{} } -func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if err := decodeRequestData(r, &h.Request); err != nil { - log.Println(err.Error()) +func New(e *Env, h *Handler) *Handler { + return &Handler{e, h.Handle, h.Request, h.Response, Set{}} +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := decodeRequestData(r, h.Request); err != nil { + log.Println("err_ServeHTTP:", err.Error()) + + w.WriteHeader(http.StatusInternalServerError) } - res, err := h.Handle(h.Env, h.Request, w) + h.Params = mux.Vars(r) + res, code, err := h.Handle(h, w) - encodeResponse(w, res, err) + encodeResponse(w, &response{code, res}, err) } func decodeRequestData(r *http.Request, v interface{}) error { @@ -43,32 +55,23 @@ func decodeRequestData(r *http.Request, v interface{}) error { rdr := ioutil.NopCloser(bytes.NewReader(buf)) r.Body = ioutil.NopCloser(bytes.NewReader(buf)) - if len(buf) == 0 { - // log.Println("empty request body1") //FIXME #1 - - return nil - } - - if err := json.NewDecoder(rdr).Decode(&v); err != nil { - return err - } + json.NewDecoder(rdr).Decode(&v) return nil } -func encodeResponse(w http.ResponseWriter, res interface{}, err error) { +func encodeResponse(w http.ResponseWriter, res *response, err error) { if err != nil { - encodeError(w, err) + encodeError(w, res.Status, err) return } - json.NewEncoder(w).Encode(res) + w.WriteHeader(res.Status) + json.NewEncoder(w).Encode(res.Data) } -func encodeError(w http.ResponseWriter, e error) { - w.WriteHeader(http.StatusInternalServerError) +func encodeError(w http.ResponseWriter, status int, e error) { + w.WriteHeader(status) - json.NewEncoder(w).Encode(map[string]interface{}{ - "error": e.Error(), - }) + json.NewEncoder(w).Encode(Error(e.Error())) } diff --git a/internal/app/handler/health_check.go b/internal/app/handler/health_check.go index 874c167..6681dc9 100644 --- a/internal/app/handler/health_check.go +++ b/internal/app/handler/health_check.go @@ -26,11 +26,15 @@ type HealthCheckResponseBody struct { Message string `json:"message,omitempty"` } -func HealthCheckHandlerFunc(_ *Env, _ interface{}, w http.ResponseWriter) (interface{}, error) { - return &HealthCheckResponse{ - Status: http.StatusText(http.StatusOK), - Data: &HealthCheckResponseBody{ - Message: "This is welcome health message. Everything seems to be alright ;)", - }, - }, nil +func HealthCheckHandlerFunc(_ *Handler, w http.ResponseWriter) (interface{}, int, error) { + return &HealthCheckResponseBody{ + Message: "This is welcome health message. Everything seems to be alright ;)", + }, http.StatusOK, nil + + // return &HealthCheckResponse{ + // Status: http.StatusText(http.StatusOK), + // Data: &HealthCheckResponseBody{ + // Message: "This is welcome health message. Everything seems to be alright ;)", + // }, + // }, http.StatusOK, nil } diff --git a/internal/app/router.go b/internal/app/router.go index 8c4fa6c..78f4bec 100644 --- a/internal/app/router.go +++ b/internal/app/router.go @@ -23,6 +23,7 @@ func SetupRouter(env *handler.Env) *mux.Router { api.Handle("/article", handler.New(env, handler.CreateArticleHandler)).Methods(http.MethodPost) api.Handle("/category", handler.New(env, handler.CreateCategoryHandler)).Methods(http.MethodPost) + api.Handle("/category/{id:[0-9]+}", handler.New(env, handler.DeleteCategoryHandler)).Methods(http.MethodDelete) return r } diff --git a/internal/app/server.go b/internal/app/server.go index f551e7e..e2f59ea 100644 --- a/internal/app/server.go +++ b/internal/app/server.go @@ -34,8 +34,6 @@ func (s *Server) Start() { } } -func (s *Server) ExecuteHandler() {} - // func (s *Server) Shutdown(ctx context.Context) { // log.Println("Shutting down...") @@ -59,16 +57,22 @@ func ValidateJsonBodyMiddleware(next http.Handler) http.Handler { buf, _ := ioutil.ReadAll(r.Body) r.Body = ioutil.NopCloser(bytes.NewReader(buf)) // rollack *Request to original state - if len(buf) == 0 { - // log.Println("empty request body2") // FIXME #1 - next.ServeHTTP(w, r) + // if len(buf) > 0 { + // next.ServeHTTP(w, r) - return - } + // return + // } - if !json.Valid(buf) { - log.Println("Unable to parse JSON: ", string(buf)) - w.WriteHeader(http.StatusInternalServerError) + // if !json.Valid(buf) { + // w.WriteHeader(http.StatusBadRequest) + // json.NewEncoder(w).Encode(handler.Error("Unable to parse JSON: " + string(buf))) + + // return + // } + + if len(buf) > 0 && !json.Valid(buf) { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(handler.Error("Unable to parse JSON: " + string(buf))) return } diff --git a/test/handler/create_article_test.go b/test/handler/create_article_test.go new file mode 100644 index 0000000..4394b4a --- /dev/null +++ b/test/handler/create_article_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "git.pbiernat.dev/golang/rest-api-prototype/internal/app/handler" +) + +func TestCreateArticleHandler(t *testing.T) { + t.Run("test create article handler", func(t *testing.T) { + request := httptest.NewRequest(http.MethodPost, "/api/article", nil) + responseRecorder := httptest.NewRecorder() + + handler.CreateArticleHandler.ServeHTTP(responseRecorder, request) + + if responseRecorder.Code != http.StatusCreated { + t.Errorf("Want status '%d', got '%d'", http.StatusCreated, responseRecorder.Code) + } + + // if strings.TrimSpace(responseRecorder.Body.String()) != tc.want { + // t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body) + // } + }) +} diff --git a/test/handler/create_category_test.go b/test/handler/create_category_test.go new file mode 100644 index 0000000..2270150 --- /dev/null +++ b/test/handler/create_category_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "net/http" + "net/http/httptest" + "testing" + + "git.pbiernat.dev/golang/rest-api-prototype/internal/app/handler" +) + +func TestCreateCategoryHandler(t *testing.T) { + t.Run("test create category handler", func(t *testing.T) { + request := httptest.NewRequest(http.MethodPost, "/api/category", nil) + responseRecorder := httptest.NewRecorder() + + handler.CreateCategoryHandler.ServeHTTP(responseRecorder, request) + + if responseRecorder.Code != http.StatusCreated { + t.Errorf("Want status '%d', got '%d'", http.StatusCreated, responseRecorder.Code) + } + + // if strings.TrimSpace(responseRecorder.Body.String()) != tc.want { + // t.Errorf("Want '%s', got '%s'", tc.want, responseRecorder.Body) + // } + }) +}