From 8948a5f2198042e1ccb8dbbd74c004da5e699baa Mon Sep 17 00:00:00 2001 From: konrad Date: Wed, 23 Oct 2019 21:11:40 +0000 Subject: [PATCH] Improve pagination (#105) --- config.yml.sample | 4 +- .../doc/practical-instructions/feature.md | 2 +- docs/content/doc/setup/config.md | 4 +- go.mod | 14 +- go.sum | 41 +++ pkg/config/config.go | 4 +- pkg/models/label.go | 17 +- pkg/models/label_task.go | 32 +- pkg/models/label_task_test.go | 2 +- pkg/models/label_test.go | 2 +- pkg/models/link_sharing.go | 25 +- pkg/models/list.go | 45 ++- pkg/models/list_read_test.go | 5 +- pkg/models/list_team.go | 26 +- pkg/models/list_team_test.go | 8 +- pkg/models/list_users.go | 22 +- pkg/models/list_users_test.go | 2 +- pkg/models/models.go | 8 +- pkg/models/namespace.go | 42 ++- pkg/models/namespace_team.go | 22 +- pkg/models/namespace_team_test.go | 6 +- pkg/models/namespace_test.go | 2 +- pkg/models/namespace_users.go | 22 +- pkg/models/namespace_users_test.go | 2 +- pkg/models/task_assignees.go | 24 +- pkg/models/task_attachment.go | 19 +- pkg/models/task_attachment_test.go | 2 +- pkg/models/task_readall_test.go | 2 +- pkg/models/tasks.go | 65 ++-- pkg/models/teams.go | 22 +- pkg/models/teams_test.go | 2 +- pkg/routes/caldav/listStorageProvider.go | 2 +- pkg/routes/routes.go | 6 + pkg/swagger/docs.go | 114 ++++++- pkg/swagger/swagger.json | 112 ++++++- pkg/swagger/swagger.yaml | 101 ++++++- vendor/code.vikunja.io/web/Readme.md | 36 ++- vendor/code.vikunja.io/web/handler/config.go | 9 + .../code.vikunja.io/web/handler/read_all.go | 47 ++- vendor/code.vikunja.io/web/web.go | 2 +- vendor/github.com/labstack/echo/v4/bind.go | 21 +- vendor/github.com/labstack/echo/v4/context.go | 7 + vendor/github.com/labstack/echo/v4/echo.go | 43 +-- vendor/github.com/labstack/echo/v4/go.mod | 10 +- vendor/github.com/labstack/echo/v4/go.sum | 47 +-- .../labstack/echo/v4/middleware/logger.go | 6 +- .../labstack/echo/v4/middleware/proxy.go | 13 +- .../labstack/echo/v4/middleware/proxy_1_11.go | 3 +- .../labstack/echo/v4/middleware/secure.go | 8 + vendor/github.com/labstack/echo/v4/router.go | 16 +- .../github.com/labstack/gommon/bytes/bytes.go | 3 +- .../mattn/go-colorable/colorable_appengine.go | 6 +- .../mattn/go-colorable/colorable_others.go | 6 +- .../mattn/go-colorable/colorable_windows.go | 43 ++- vendor/github.com/mattn/go-colorable/go.sum | 2 - .../mattn/go-colorable/noncolorable.go | 6 +- .../github.com/valyala/fasttemplate/README.md | 2 +- .../x/sys/unix/sockcmsg_dragonfly.go | 16 + .../golang.org/x/sys/unix/sockcmsg_linux.go | 2 +- vendor/golang.org/x/sys/unix/sockcmsg_unix.go | 36 +-- .../x/sys/unix/sockcmsg_unix_other.go | 38 +++ vendor/golang.org/x/sys/unix/syscall_bsd.go | 2 +- .../golang.org/x/sys/unix/syscall_darwin.go | 2 + .../x/sys/unix/syscall_darwin_386.go | 1 - .../x/sys/unix/syscall_darwin_amd64.go | 1 - .../x/sys/unix/syscall_darwin_arm.go | 4 - .../x/sys/unix/syscall_darwin_arm64.go | 4 - .../x/sys/unix/syscall_dragonfly.go | 22 +- .../golang.org/x/sys/unix/syscall_freebsd.go | 4 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 2 +- .../golang.org/x/sys/unix/syscall_netbsd.go | 4 +- .../golang.org/x/sys/unix/syscall_openbsd.go | 4 +- .../golang.org/x/sys/unix/syscall_solaris.go | 2 +- .../x/sys/unix/zsyscall_darwin_386.1_11.go | 32 +- .../x/sys/unix/zsyscall_darwin_386.go | 42 +-- .../x/sys/unix/zsyscall_darwin_386.s | 6 +- .../x/sys/unix/zsyscall_darwin_amd64.1_11.go | 32 +- .../x/sys/unix/zsyscall_darwin_amd64.go | 42 +-- .../x/sys/unix/zsyscall_darwin_amd64.s | 4 +- .../x/sys/unix/zsyscall_darwin_arm.1_11.go | 16 + .../x/sys/unix/zsyscall_darwin_arm.go | 21 ++ .../x/sys/unix/zsyscall_darwin_arm64.1_11.go | 16 + .../x/sys/unix/zsyscall_darwin_arm64.go | 21 ++ .../x/sys/unix/zsyscall_darwin_arm64.s | 4 +- .../x/tools/go/internal/gcimporter/iexport.go | 25 +- .../x/tools/go/internal/gcimporter/iimport.go | 28 +- .../golang.org/x/tools/go/packages/golist.go | 186 +++++++++--- .../x/tools/go/packages/golist_overlay.go | 21 +- .../x/tools/internal/gopathwalk/walk.go | 6 +- .../golang.org/x/tools/internal/span/parse.go | 100 ++++++ .../golang.org/x/tools/internal/span/span.go | 285 ++++++++++++++++++ .../golang.org/x/tools/internal/span/token.go | 151 ++++++++++ .../x/tools/internal/span/token111.go | 39 +++ .../x/tools/internal/span/token112.go | 16 + .../golang.org/x/tools/internal/span/uri.go | 152 ++++++++++ .../golang.org/x/tools/internal/span/utf16.go | 94 ++++++ vendor/modules.txt | 17 +- 97 files changed, 2137 insertions(+), 529 deletions(-) create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go create mode 100644 vendor/golang.org/x/tools/internal/span/parse.go create mode 100644 vendor/golang.org/x/tools/internal/span/span.go create mode 100644 vendor/golang.org/x/tools/internal/span/token.go create mode 100644 vendor/golang.org/x/tools/internal/span/token111.go create mode 100644 vendor/golang.org/x/tools/internal/span/token112.go create mode 100644 vendor/golang.org/x/tools/internal/span/uri.go create mode 100644 vendor/golang.org/x/tools/internal/span/utf16.go diff --git a/config.yml.sample b/config.yml.sample index 860942caa7..25cd024b19 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -11,8 +11,8 @@ service: # Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder # with a config file which will then be used. rootpath: - # The number of items which gets returned per page - pagecount: 50 + # The max number of items which can be returned per page + maxitemsperpage: 50 # If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system # You'll need to use redis for this in order to enable common metrics over multiple nodes enablemetrics: false diff --git a/docs/content/doc/practical-instructions/feature.md b/docs/content/doc/practical-instructions/feature.md index 059ab6a340..0c50caf394 100644 --- a/docs/content/doc/practical-instructions/feature.md +++ b/docs/content/doc/practical-instructions/feature.md @@ -27,7 +27,7 @@ You can feed this function directly into xorm's `Limit`-Function like so: {{< highlight golang >}} lists := []List{} -err := x.Limit(getLimitFromPageIndex(pageIndex)).Find(&lists) +err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&lists) {{< /highlight >}} // TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint? diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md index 4d84056949..5cba06d265 100644 --- a/docs/content/doc/setup/config.md +++ b/docs/content/doc/setup/config.md @@ -54,8 +54,8 @@ service: # Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder # with a config file which will then be used. rootpath: - # The number of items which gets returned per page - pagecount: 50 + # The max number of items which can be returned per page + maxitemsperpage: 50 # If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system # You'll need to use redis for this in order to enable common metrics over multiple nodes enablemetrics: false diff --git a/go.mod b/go.mod index cb106c23b6..aa5493607e 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ module code.vikunja.io/api require ( cloud.google.com/go v0.34.0 // indirect - code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332 + code.vikunja.io/web v0.0.0-20191023202526-f337750c3573 github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a github.com/beevik/etree v1.1.0 // indirect @@ -46,10 +46,11 @@ require ( github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb github.com/json-iterator/go v1.1.7 // indirect github.com/kr/pty v1.1.8 // indirect - github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f - github.com/labstack/gommon v0.2.9 + github.com/labstack/echo/v4 v4.1.11 + github.com/labstack/gommon v0.3.0 github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef github.com/mailru/easyjson v0.7.0 // indirect + github.com/mattn/go-colorable v0.1.4 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect github.com/mattn/go-runewidth v0.0.4 // indirect @@ -73,12 +74,13 @@ require ( github.com/ugorji/go v1.1.7 // indirect github.com/ulule/limiter/v3 v3.3.0 github.com/urfave/cli v1.22.1 // indirect + github.com/valyala/fasttemplate v1.1.0 // indirect golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/lint v0.0.0-20190409202823-959b441ac422 - golang.org/x/net v0.0.0-20191011234655-491137f69257 // indirect + golang.org/x/net v0.0.0-20191021144547-ec77196f6094 // indirect golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect - golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a // indirect + golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 // indirect + golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 // indirect golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect google.golang.org/appengine v1.5.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/go.sum b/go.sum index a38c00714b..fab9eb85b2 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,20 @@ code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7 h1:P9ncMaJE7RbYqBXF9lwT0h code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332 h1:gXxyLkjhgN+vqrLvPyqyScyG5fbu44FJp61TvntWM24= code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174 h1:hBY+r6bzGEfHxolaXbiVoz2LBNNnyHZK7d7Ga4Jowu8= +code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a h1:exDC9eZ+SK0GT3zB/5f3OBahWzbTZlvX9OfZWgqlbeI= +code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79 h1:U2px27G/b082nUu8vO21wFNKF9BM+5YQJj4XRZiyn2I= +code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d h1:zhNidbAwqJSnkql03i7aHDUMyQo1vM8yR1Ks495FKvc= +code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d h1:Fw5eiTr4p82l4PLaML1ARgx3fjyebxVNvPsCz727brk= +code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa h1:rtYKpdT/6wGgxGNFUzl9Q/AHgS778+rSC20AcBPNu/I= +code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= +code.vikunja.io/web v0.0.0-20191023202526-f337750c3573 h1:q+nf3ao4vLpoAaksuk6lkRAMAcD2grOPNj/HwjejLl4= +code.vikunja.io/web v0.0.0-20191023202526-f337750c3573/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= @@ -170,10 +184,14 @@ github.com/labstack/echo/v4 v4.1.6 h1:WOvLa4T1KzWCRpANwz0HGgWDelXSSGwIKtKBbFdHTv github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f h1:fNJtR+TNyxTdYCZU40fc8Or8RyBqMOKYNv+Zay5gjvk= github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE= +github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww= +github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU= github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4= github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= @@ -193,6 +211,8 @@ github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0X github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -200,6 +220,7 @@ github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 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.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8= @@ -312,6 +333,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4= +github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= @@ -326,6 +349,7 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= @@ -352,6 +376,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191011234655-491137f69257 h1:ry8e2D+cwaV6hk7lb3aRTjjZo24shrbK0e11QEOkTIg= golang.org/x/net v0.0.0-20191011234655-491137f69257/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -375,9 +401,16 @@ golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oae golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191023145028-b69606af412f h1:HNixo/W24k2W4EliZfUFl5ApIz/dMDShw52wmWfJ8/s= +golang.org/x/sys v0.0.0-20191023145028-b69606af412f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0= +golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -396,6 +429,14 @@ golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqW golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a h1:TwMENskLwU2NnWBzrJGEWHqSiGUkO/B4rfyhwqDxDYQ= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100 h1:OT2Y8iVtXGHPODZd6iwpndJmAYRiZc75IYxlufvlkLg= +golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12 h1:s9/f9YHBWfC3jIKMbJElk5+EwgC58Khn6t1EdLnQ9+k= +golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3 h1:4haCIJia9wHJUU7z9f7PTC8Nf599Ok93njSCHb5gJas= +golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 h1:0vQisIa3mUFShxg7Xyq8WFt/ArQ1soDk5A5uF62IJCc= +golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 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= google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= diff --git a/pkg/config/config.go b/pkg/config/config.go index 1dad016740..6cd5ecea2b 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -38,7 +38,7 @@ const ( ServiceFrontendurl Key = `service.frontendurl` ServiceEnableCaldav Key = `service.enablecaldav` ServiceRootpath Key = `service.rootpath` - ServicePageCount Key = `service.pagecount` + ServiceMaxItemsPerPage Key = `service.maxitemsperpage` ServiceEnableMetrics Key = `service.enablemetrics` ServiceMotd Key = `service.motd` ServiceEnableLinkSharing Key = `service.enablelinksharing` @@ -146,7 +146,7 @@ func InitDefaultConfig() { } exPath := filepath.Dir(ex) ServiceRootpath.setDefault(exPath) - ServicePageCount.setDefault(50) + ServiceMaxItemsPerPage.setDefault(50) ServiceEnableMetrics.setDefault(false) ServiceMotd.setDefault("") ServiceEnableLinkSharing.setDefault(true) diff --git a/pkg/models/label.go b/pkg/models/label.go index 6684171250..88b2f31f3d 100644 --- a/pkg/models/label.go +++ b/pkg/models/label.go @@ -124,15 +124,16 @@ func (l *Label) Delete() (err error) { // @tags labels // @Accept json // @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search labels by label text." // @Security JWTKeyAuth // @Success 200 {array} models.Label "The labels" // @Failure 500 {object} models.Message "Internal error" // @Router /labels [get] -func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, err error) { +func (l *Label) ReadAll(a web.Auth, search string, page int, perPage int) (ls interface{}, resultCount int, numberOfEntries int64, err error) { if _, is := a.(*LinkSharing); is { - return nil, ErrGenericForbidden{} + return nil, 0, 0, ErrGenericForbidden{} } u := &User{ID: a.GetID()} @@ -140,13 +141,15 @@ func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, er // Get all tasks taskIDs, err := getUserTaskIDs(u) if err != nil { - return nil, err + return nil, 0, 0, err } return getLabelsByTaskIDs(&LabelByTaskIDsOptions{ Search: search, User: u, TaskIDs: taskIDs, + Page: page, + PerPage: perPage, GetUnusedLabels: true, GroupByLabelIDsOnly: true, }) @@ -198,15 +201,17 @@ func getLabelByIDSimple(labelID int64) (*Label, error) { func getUserTaskIDs(u *User) (taskIDs []int64, err error) { // Get all lists - lists, err := getRawListsForUser("", u, -1) + lists, _, _, err := getRawListsForUser("", u, -1, 0) if err != nil { return nil, err } - tasks, err := getRawTasksForLists(lists, &taskOptions{ + tasks, _, _, err := getRawTasksForLists(lists, &taskOptions{ startDate: time.Unix(0, 0), endDate: time.Unix(0, 0), sortby: SortTasksByUnsorted, + page: -1, + perPage: 0, }) if err != nil { return nil, err diff --git a/pkg/models/label_task.go b/pkg/models/label_task.go index 707e5e0f2a..00dc8fc9e4 100644 --- a/pkg/models/label_task.go +++ b/pkg/models/label_task.go @@ -101,21 +101,22 @@ func (lt *LabelTask) Create(a web.Auth) (err error) { // @Accept json // @Produce json // @Param task path int true "Task ID" -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search labels by label text." // @Security JWTKeyAuth // @Success 200 {array} models.Label "The labels" // @Failure 500 {object} models.Message "Internal error" // @Router /tasks/{task}/labels [get] -func (lt *LabelTask) ReadAll(search string, a web.Auth, page int) (labels interface{}, err error) { +func (lt *LabelTask) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { // Check if the user has the right to see the task task := Task{ID: lt.TaskID} canRead, err := task.CanRead(a) if err != nil { - return nil, err + return nil, 0, 0, err } if !canRead { - return nil, ErrNoRightToSeeTask{lt.TaskID, a.GetID()} + return nil, 0, 0, ErrNoRightToSeeTask{lt.TaskID, a.GetID()} } return getLabelsByTaskIDs(&LabelByTaskIDsOptions{ @@ -137,6 +138,7 @@ type LabelByTaskIDsOptions struct { User *User Search string Page int + PerPage int TaskIDs []int64 GetUnusedLabels bool GroupByLabelIDsOnly bool @@ -144,7 +146,7 @@ type LabelByTaskIDsOptions struct { // Helper function to get all labels for a set of tasks // Used when getting all labels for one task as well when getting all lables -func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err error) { +func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, resultCount int, totalEntries int64, err error) { // Include unused labels. Needed to be able to show a list of all unused labels a user // has access to. var uidOrNil interface{} @@ -172,10 +174,10 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err Or(builder.In("label_task.task_id", opts.TaskIDs)). And("labels.title LIKE ?", "%"+opts.Search+"%"). GroupBy(groupBy). - Limit(getLimitFromPageIndex(opts.Page)). + Limit(getLimitFromPageIndex(opts.Page, opts.PerPage)). Find(&labels) if err != nil { - return nil, err + return nil, 0, 0, err } // Get all created by users @@ -186,7 +188,7 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err users := make(map[int64]*User) err = x.In("id", userids).Find(&users) if err != nil { - return nil, err + return nil, 0, 0, err } // Obfuscate all user emails @@ -199,7 +201,19 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err labels[in].CreatedBy = users[l.CreatedByID] } - return labels, err + // Get the total number of entries + totalEntries, err = x.Table("labels"). + Join("LEFT", "label_task", "label_task.label_id = labels.id"). + Where(requestOrNil, uidOrNil). + Or(builder.In("label_task.task_id", opts.TaskIDs)). + And("labels.title LIKE ?", "%"+opts.Search+"%"). + GroupBy(groupBy). + Count(&Label{}) + if err != nil { + return nil, 0, 0, err + } + + return labels, len(labels), totalEntries, err } // Create or update a bunch of task labels diff --git a/pkg/models/label_task_test.go b/pkg/models/label_task_test.go index 5f8299d3ce..5d619857fa 100644 --- a/pkg/models/label_task_test.go +++ b/pkg/models/label_task_test.go @@ -89,7 +89,7 @@ func TestLabelTask_ReadAll(t *testing.T) { CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } - gotLabels, err := l.ReadAll(tt.args.search, tt.args.a, tt.args.page) + gotLabels, _, _, err := l.ReadAll(tt.args.a, tt.args.search, tt.args.page, 0) if (err != nil) != tt.wantErr { t.Errorf("LabelTask.ReadAll() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/models/label_test.go b/pkg/models/label_test.go index 88cb0fb912..fb2a364070 100644 --- a/pkg/models/label_test.go +++ b/pkg/models/label_test.go @@ -117,7 +117,7 @@ func TestLabel_ReadAll(t *testing.T) { CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } - gotLs, err := l.ReadAll(tt.args.search, tt.args.a, tt.args.page) + gotLs, _, _, err := l.ReadAll(tt.args.a, tt.args.search, tt.args.page, 0) if (err != nil) != tt.wantErr { t.Errorf("Label.ReadAll() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/models/link_sharing.go b/pkg/models/link_sharing.go index 8ac00a1011..51251f97f0 100644 --- a/pkg/models/link_sharing.go +++ b/pkg/models/link_sharing.go @@ -136,29 +136,30 @@ func (share *LinkSharing) ReadOne() (err error) { // @Accept json // @Produce json // @Param list path int true "List ID" -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search shares by hash." // @Security JWTKeyAuth // @Success 200 {array} models.LinkSharing "The share links" // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{list}/shares [get] -func (share *LinkSharing) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (share *LinkSharing) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { list := &List{ID: share.ListID} can, err := list.CanRead(a) if err != nil { - return nil, err + return nil, 0, 0, err } if !can { - return nil, ErrGenericForbidden{} + return nil, 0, 0, ErrGenericForbidden{} } var shares []*LinkSharing err = x. Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%"). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Find(&shares) if err != nil { - return nil, err + return nil, 0, 0, err } // Find all users and add them @@ -170,14 +171,22 @@ func (share *LinkSharing) ReadAll(search string, a web.Auth, page int) (interfac users := make(map[int64]*User) err = x.In("id", userIDs).Find(&users) if err != nil { - return nil, err + return nil, 0, 0, err } for _, s := range shares { s.SharedBy = users[s.SharedByID] } - return shares, err + // Total count + totalItems, err = x. + Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%"). + Count(&LinkSharing{}) + if err != nil { + return nil, 0, 0, err + } + + return shares, len(shares), totalItems, err } // Delete removes a link share diff --git a/pkg/models/list.go b/pkg/models/list.go index aa822d0fe6..e09324f0eb 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -77,35 +77,36 @@ func GetListsByNamespaceID(nID int64, doer *User) (lists []*List, err error) { // @tags list // @Accept json // @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search lists by title." // @Security JWTKeyAuth // @Success 200 {array} models.List "The lists" // @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list" // @Failure 500 {object} models.Message "Internal error" // @Router /lists [get] -func (l *List) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (l *List) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { // Check if we're dealing with a share auth shareAuth, ok := a.(*LinkSharing) if ok { list := &List{ID: shareAuth.ListID} err := list.GetSimpleByID() if err != nil { - return nil, err + return nil, 0, 0, err } lists := []*List{list} err = AddListDetails(lists) - return lists, err + return lists, 0, 0, err } - lists, err := getRawListsForUser(search, &User{ID: a.GetID()}, page) + lists, resultCount, totalItems, err := getRawListsForUser(search, &User{ID: a.GetID()}, page, perPage) if err != nil { - return nil, err + return nil, 0, 0, err } // Add more list details err = AddListDetails(lists) - return lists, err + return lists, resultCount, totalItems, err } // ReadOne gets one list by its ID @@ -177,10 +178,10 @@ func GetListSimplByTaskID(taskID int64) (l *List, err error) { } // Gets the lists only, without any tasks or so -func getRawListsForUser(search string, u *User, page int) (lists []*List, err error) { +func getRawListsForUser(search string, u *User, page int, perPage int) (lists []*List, resultCount int, totalItems int64, err error) { fullUser, err := GetUserByID(u.ID) if err != nil { - return lists, err + return nil, 0, 0, err } // Gets all Lists where the user is either owner or in a team which has access to the list @@ -201,11 +202,33 @@ func getRawListsForUser(search string, u *User, page int) (lists []*List, err er Or("ul.user_id = ?", fullUser.ID). Or("un.user_id = ?", fullUser.ID). GroupBy("l.id"). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Where("l.title LIKE ?", "%"+search+"%"). Find(&lists) + if err != nil { + return nil, 0, 0, err + } - return lists, err + totalItems, err = x. + Table("list"). + Alias("l"). + Join("INNER", []string{"namespaces", "n"}, "l.namespace_id = n.id"). + Join("LEFT", []string{"team_namespaces", "tn"}, "tn.namespace_id = n.id"). + Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id"). + Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id"). + Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id"). + Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id"). + Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id"). + Where("tm.user_id = ?", fullUser.ID). + Or("tm2.user_id = ?", fullUser.ID). + Or("l.owner_id = ?", fullUser.ID). + Or("ul.user_id = ?", fullUser.ID). + Or("un.user_id = ?", fullUser.ID). + GroupBy("l.id"). + Limit(getLimitFromPageIndex(page, perPage)). + Where("l.title LIKE ?", "%"+search+"%"). + Count(&List{}) + return lists, len(lists), totalItems, err } // AddListDetails adds owner user objects and list tasks to all lists in the slice diff --git a/pkg/models/list_read_test.go b/pkg/models/list_read_test.go index c796085f0c..3c334ed05a 100644 --- a/pkg/models/list_read_test.go +++ b/pkg/models/list_read_test.go @@ -36,14 +36,15 @@ func TestList_ReadAll(t *testing.T) { assert.NoError(t, err) lists2 := List{} - lists3, err := lists2.ReadAll("", u, 1) + lists3, _, _, err := lists2.ReadAll(u, "", 1, 50) + assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) s := reflect.ValueOf(lists3) assert.Equal(t, 16, s.Len()) // Try getting lists for a nonexistant user - _, err = lists2.ReadAll("", &User{ID: 984234}, 1) + _, _, _, err = lists2.ReadAll(&User{ID: 984234}, "", 1, 50) assert.Error(t, err) assert.True(t, IsErrUserDoesNotExist(err)) } diff --git a/pkg/models/list_team.go b/pkg/models/list_team.go index 9115e2b84f..67e1ca2a77 100644 --- a/pkg/models/list_team.go +++ b/pkg/models/list_team.go @@ -154,22 +154,23 @@ func (tl *TeamList) Delete() (err error) { // @Accept json // @Produce json // @Param id path int true "List ID" -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search teams by its name." // @Security JWTKeyAuth // @Success 200 {array} models.TeamWithRight "The teams with their right." // @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list." // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{id}/teams [get] -func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (tl *TeamList) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { // Check if the user can read the namespace l := &List{ID: tl.ListID} canRead, err := l.CanRead(a) if err != nil { - return nil, err + return nil, 0, 0, err } if !canRead { - return nil, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()} + return nil, 0, 0, ErrNeedToHaveListReadAccess{ListID: tl.ListID, UserID: a.GetID()} } // Get the teams @@ -178,11 +179,24 @@ func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, e Table("teams"). Join("INNER", "team_list", "team_id = teams.id"). Where("team_list.list_id = ?", tl.ListID). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Where("teams.name LIKE ?", "%"+search+"%"). Find(&all) + if err != nil { + return nil, 0, 0, err + } - return all, err + totalItems, err = x. + Table("teams"). + Join("INNER", "team_list", "team_id = teams.id"). + Where("team_list.list_id = ?", tl.ListID). + Where("teams.name LIKE ?", "%"+search+"%"). + Count(&TeamWithRight{}) + if err != nil { + return nil, 0, 0, err + } + + return all, len(all), totalItems, err } // Update updates a team <-> list relation diff --git a/pkg/models/list_team_test.go b/pkg/models/list_team_test.go index b7c50633ee..293ec3067b 100644 --- a/pkg/models/list_team_test.go +++ b/pkg/models/list_team_test.go @@ -69,27 +69,27 @@ func TestTeamList(t *testing.T) { assert.True(t, IsErrListDoesNotExist(err)) // Test Read all - teams, err := tl.ReadAll("", u, 1) + teams, _, _, err := tl.ReadAll(u, "", 1, 50) assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice) s := reflect.ValueOf(teams) assert.Equal(t, s.Len(), 1) // Test Read all for nonexistant list - _, err = tl4.ReadAll("", u, 1) + _, _, _, err = tl4.ReadAll(u, "", 1, 50) assert.Error(t, err) assert.True(t, IsErrListDoesNotExist(err)) // Test Read all for a list where the user is owner of the namespace this list belongs to tl5 := tl tl5.ListID = 2 - _, err = tl5.ReadAll("", u, 1) + _, _, _, err = tl5.ReadAll(u, "", 1, 50) assert.NoError(t, err) // Test read all for a list where the user not has access tl6 := tl tl6.ListID = 5 - _, err = tl6.ReadAll("", u, 1) + _, _, _, err = tl6.ReadAll(u, "", 1, 50) assert.Error(t, err) assert.True(t, IsErrNeedToHaveListReadAccess(err)) diff --git a/pkg/models/list_users.go b/pkg/models/list_users.go index 571e4d3fcd..001c8dc697 100644 --- a/pkg/models/list_users.go +++ b/pkg/models/list_users.go @@ -159,22 +159,23 @@ func (lu *ListUser) Delete() (err error) { // @Accept json // @Produce json // @Param id path int true "List ID" -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search users by its name." // @Security JWTKeyAuth // @Success 200 {array} models.UserWithRight "The users with the right they have." // @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the list." // @Failure 500 {object} models.Message "Internal error" // @Router /lists/{id}/users [get] -func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (lu *ListUser) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { // Check if the user has access to the list l := &List{ID: lu.ListID} canRead, err := l.CanRead(a) if err != nil { - return nil, err + return nil, 0, 0, err } if !canRead { - return nil, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID} + return nil, 0, 0, ErrNeedToHaveListReadAccess{UserID: a.GetID(), ListID: lu.ListID} } // Get all users @@ -182,16 +183,25 @@ func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, e err = x. Join("INNER", "users_list", "user_id = users.id"). Where("users_list.list_id = ?", lu.ListID). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Where("users.username LIKE ?", "%"+search+"%"). Find(&all) + if err != nil { + return nil, 0, 0, err + } // Obfuscate all user emails for _, u := range all { u.Email = "" } - return all, err + numberOfTotalItems, err = x. + Join("INNER", "users_list", "user_id = users.id"). + Where("users_list.list_id = ?", lu.ListID). + Where("users.username LIKE ?", "%"+search+"%"). + Count(&UserWithRight{}) + + return all, len(all), numberOfTotalItems, err } // Update updates a user <-> list relation diff --git a/pkg/models/list_users_test.go b/pkg/models/list_users_test.go index 2b94e7ef9e..64144888f2 100644 --- a/pkg/models/list_users_test.go +++ b/pkg/models/list_users_test.go @@ -203,7 +203,7 @@ func TestListUser_ReadAll(t *testing.T) { CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } - got, err := ul.ReadAll(tt.args.search, tt.args.a, tt.args.page) + got, _, _, err := ul.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50) if (err != nil) != tt.wantErr { t.Errorf("ListUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr) } diff --git a/pkg/models/models.go b/pkg/models/models.go index 36d9387f64..8ea422a8a1 100644 --- a/pkg/models/models.go +++ b/pkg/models/models.go @@ -69,14 +69,18 @@ func SetEngine() (err error) { return nil } -func getLimitFromPageIndex(page int) (limit, start int) { +func getLimitFromPageIndex(page int, perPage int) (limit, start int) { // Get everything when page index is -1 if page < 0 { return 0, 0 } - limit = config.ServicePageCount.GetInt() + limit = config.ServiceMaxItemsPerPage.GetInt() + if perPage > 0 { + limit = perPage + } + start = limit * (page - 1) return } diff --git a/pkg/models/namespace.go b/pkg/models/namespace.go index 5e049ed4af..927f57ebc1 100644 --- a/pkg/models/namespace.go +++ b/pkg/models/namespace.go @@ -131,20 +131,21 @@ type NamespaceWithLists struct { // @tags namespace // @Accept json // @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search namespaces by name." // @Security JWTKeyAuth // @Success 200 {array} models.NamespaceWithLists "The Namespaces." // @Failure 500 {object} models.Message "Internal error" // @Router /namespaces [get] -func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (n *Namespace) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { if _, is := a.(*LinkSharing); is { - return nil, ErrGenericForbidden{} + return nil, 0, 0, ErrGenericForbidden{} } doer, err := getUserWithError(a) if err != nil { - return nil, err + return nil, 0, 0, err } all := []*NamespaceWithLists{} @@ -167,11 +168,11 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e Or("namespaces.owner_id = ?", doer.ID). Or("users_namespace.user_id = ?", doer.ID). GroupBy("namespaces.id"). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Where("namespaces.name LIKE ?", "%"+search+"%"). Find(&all) if err != nil { - return all, err + return all, 0, 0, err } // Get all users @@ -187,7 +188,7 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e Find(&users) if err != nil { - return all, err + return all, 0, 0, err } // Make a list of namespace ids @@ -202,7 +203,7 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e In("namespace_id", namespaceids). Find(&lists) if err != nil { - return all, err + return all, 0, 0, err } // Get all lists individually shared with our user (not via a namespace) @@ -218,7 +219,7 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e GroupBy("l.id"). Find(&individualLists) if err != nil { - return nil, err + return nil, 0, 0, err } // Make the namespace -1 so we now later which one it was @@ -234,9 +235,13 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e } // More details for the lists - AddListDetails(lists) + err = AddListDetails(lists) + if err != nil { + return nil, 0, 0, err + } // Put objects in our namespace list + // TODO: Refactor this to use maps for better efficiency for i, n := range all { // Users @@ -255,7 +260,22 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e } } - return all, nil + numberOfTotalItems, err = x. + Table("namespaces"). + Join("LEFT", "team_namespaces", "namespaces.id = team_namespaces.namespace_id"). + Join("LEFT", "team_members", "team_members.team_id = team_namespaces.team_id"). + Join("LEFT", "users_namespace", "users_namespace.namespace_id = namespaces.id"). + Where("team_members.user_id = ?", doer.ID). + Or("namespaces.owner_id = ?", doer.ID). + Or("users_namespace.user_id = ?", doer.ID). + GroupBy("namespaces.id"). + Where("namespaces.name LIKE ?", "%"+search+"%"). + Count(&NamespaceWithLists{}) + if err != nil { + return all, 0, 0, err + } + + return all, len(all), numberOfTotalItems, nil } // Create implements the creation method via the interface diff --git a/pkg/models/namespace_team.go b/pkg/models/namespace_team.go index bb7afb99f3..614fd3aaa7 100644 --- a/pkg/models/namespace_team.go +++ b/pkg/models/namespace_team.go @@ -139,22 +139,23 @@ func (tn *TeamNamespace) Delete() (err error) { // @Accept json // @Produce json // @Param id path int true "Namespace ID" -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search teams by its name." // @Security JWTKeyAuth // @Success 200 {array} models.TeamWithRight "The teams with the right they have." // @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace." // @Failure 500 {object} models.Message "Internal error" // @Router /namespaces/{id}/teams [get] -func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (tn *TeamNamespace) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { // Check if the user can read the namespace n := Namespace{ID: tn.NamespaceID} canRead, err := n.CanRead(a) if err != nil { - return nil, err + return nil, 0, 0, err } if !canRead { - return nil, ErrNeedToHaveNamespaceReadAccess{NamespaceID: tn.NamespaceID, UserID: a.GetID()} + return nil, 0, 0, ErrNeedToHaveNamespaceReadAccess{NamespaceID: tn.NamespaceID, UserID: a.GetID()} } // Get the teams @@ -163,11 +164,20 @@ func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface err = x.Table("teams"). Join("INNER", "team_namespaces", "team_id = teams.id"). Where("team_namespaces.namespace_id = ?", tn.NamespaceID). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Where("teams.name LIKE ?", "%"+search+"%"). Find(&all) + if err != nil { + return nil, 0, 0, err + } - return all, err + numberOfTotalItems, err = x.Table("teams"). + Join("INNER", "team_namespaces", "team_id = teams.id"). + Where("team_namespaces.namespace_id = ?", tn.NamespaceID). + Where("teams.name LIKE ?", "%"+search+"%"). + Count(&TeamWithRight{}) + + return all, len(all), numberOfTotalItems, err } // Update updates a team <-> namespace relation diff --git a/pkg/models/namespace_team_test.go b/pkg/models/namespace_team_test.go index fe4024f661..9cb114232c 100644 --- a/pkg/models/namespace_team_test.go +++ b/pkg/models/namespace_team_test.go @@ -68,20 +68,20 @@ func TestTeamNamespace(t *testing.T) { assert.True(t, IsErrNamespaceDoesNotExist(err)) // Check readall - teams, err := tn.ReadAll("", dummyuser, 1) + teams, _, _, err := tn.ReadAll(dummyuser, "", 1, 50) assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice) s := reflect.ValueOf(teams) assert.Equal(t, s.Len(), 1) // Check readall for a nonexistant namespace - _, err = tn4.ReadAll("", dummyuser, 1) + _, _, _, err = tn4.ReadAll(dummyuser, "", 1, 50) assert.Error(t, err) assert.True(t, IsErrNamespaceDoesNotExist(err)) // Check with no right to read the namespace nouser := &User{ID: 393} - _, err = tn.ReadAll("", nouser, 1) + _, _, _, err = tn.ReadAll(nouser, "", 1, 50) assert.Error(t, err) assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err)) diff --git a/pkg/models/namespace_test.go b/pkg/models/namespace_test.go index 118a76a100..58ea17b3e6 100644 --- a/pkg/models/namespace_test.go +++ b/pkg/models/namespace_test.go @@ -118,7 +118,7 @@ func TestNamespace_Create(t *testing.T) { assert.True(t, IsErrNamespaceDoesNotExist(err)) // Get all namespaces of a user - nsps, err := n.ReadAll("", doer, 1) + nsps, _, _, err := n.ReadAll(doer, "", 1, 50) assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(nsps).Kind(), reflect.Slice) s := reflect.ValueOf(nsps) diff --git a/pkg/models/namespace_users.go b/pkg/models/namespace_users.go index 228ec5021f..e9c446a13d 100644 --- a/pkg/models/namespace_users.go +++ b/pkg/models/namespace_users.go @@ -145,22 +145,23 @@ func (nu *NamespaceUser) Delete() (err error) { // @Accept json // @Produce json // @Param id path int true "Namespace ID" -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search users by its name." // @Security JWTKeyAuth // @Success 200 {array} models.UserWithRight "The users with the right they have." // @Failure 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace." // @Failure 500 {object} models.Message "Internal error" // @Router /namespaces/{id}/users [get] -func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (nu *NamespaceUser) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { // Check if the user has access to the namespace l := Namespace{ID: nu.NamespaceID} canRead, err := l.CanRead(a) if err != nil { - return nil, err + return nil, 0, 0, err } if !canRead { - return nil, ErrNeedToHaveNamespaceReadAccess{} + return nil, 0, 0, ErrNeedToHaveNamespaceReadAccess{} } // Get all users @@ -168,16 +169,25 @@ func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface err = x. Join("INNER", "users_namespace", "user_id = users.id"). Where("users_namespace.namespace_id = ?", nu.NamespaceID). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Where("users.username LIKE ?", "%"+search+"%"). Find(&all) + if err != nil { + return nil, 0, 0, err + } // Obfuscate all user emails for _, u := range all { u.Email = "" } - return all, err + numberOfTotalItems, err = x. + Join("INNER", "users_namespace", "user_id = users.id"). + Where("users_namespace.namespace_id = ?", nu.NamespaceID). + Where("users.username LIKE ?", "%"+search+"%"). + Count(&UserWithRight{}) + + return all, len(all), numberOfTotalItems, err } // Update updates a user <-> namespace relation diff --git a/pkg/models/namespace_users_test.go b/pkg/models/namespace_users_test.go index 80d5ef9d65..d72ccc1e30 100644 --- a/pkg/models/namespace_users_test.go +++ b/pkg/models/namespace_users_test.go @@ -202,7 +202,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) { CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } - got, err := un.ReadAll(tt.args.search, tt.args.a, tt.args.page) + got, _, _, err := un.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50) if (err != nil) != tt.wantErr { t.Errorf("NamespaceUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/models/task_assignees.go b/pkg/models/task_assignees.go index d4c747a457..e7de9af1ed 100644 --- a/pkg/models/task_assignees.go +++ b/pkg/models/task_assignees.go @@ -219,25 +219,26 @@ func (t *Task) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) { // @tags assignees // @Accept json // @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search assignees by their username." // @Param taskID path int true "Task ID" // @Security JWTKeyAuth // @Success 200 {array} models.User "The assignees" // @Failure 500 {object} models.Message "Internal error" // @Router /tasks/{taskID}/assignees [get] -func (la *TaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (la *TaskAssginee) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { task, err := GetListSimplByTaskID(la.TaskID) if err != nil { - return nil, err + return nil, 0, 0, err } can, err := task.CanRead(a) if err != nil { - return nil, err + return nil, 0, 0, err } if !can { - return nil, ErrGenericForbidden{} + return nil, 0, 0, ErrGenericForbidden{} } var taskAssignees []*User @@ -245,9 +246,18 @@ func (la *TaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{ Select("users.*"). Join("INNER", "users", "task_assignees.user_id = users.id"). Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%"). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Find(&taskAssignees) - return taskAssignees, err + if err != nil { + return nil, 0, 0, err + } + + numberOfTotalItems, err = x.Table("task_assignees"). + Select("users.*"). + Join("INNER", "users", "task_assignees.user_id = users.id"). + Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%"). + Count(&User{}) + return taskAssignees, len(taskAssignees), numberOfTotalItems, err } // BulkAssignees is a helper struct used to update multiple assignees at once. diff --git a/pkg/models/task_attachment.go b/pkg/models/task_attachment.go index cbd30d3651..03576a8808 100644 --- a/pkg/models/task_attachment.go +++ b/pkg/models/task_attachment.go @@ -100,21 +100,23 @@ func (ta *TaskAttachment) ReadOne() (err error) { // @Accept json // @Produce json // @Param id path int true "Task ID" +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Security JWTKeyAuth // @Success 200 {array} models.TaskAttachment "All attachments for this task" // @Failure 403 {object} models.Message "No access to this task." // @Failure 404 {object} models.Message "The task does not exist." // @Failure 500 {object} models.Message "Internal error" // @Router /tasks/{id}/attachments [get] -func (ta *TaskAttachment) ReadAll(s string, a web.Auth, page int) (interface{}, error) { +func (ta *TaskAttachment) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { attachments := []*TaskAttachment{} - err := x. - Limit(getLimitFromPageIndex(page)). + err = x. + Limit(getLimitFromPageIndex(page, perPage)). Where("task_id = ?", ta.TaskID). Find(&attachments) if err != nil { - return nil, err + return nil, 0, 0, err } fileIDs := make([]int64, 0, len(attachments)) @@ -127,13 +129,13 @@ func (ta *TaskAttachment) ReadAll(s string, a web.Auth, page int) (interface{}, fs := make(map[int64]*files.File) err = x.In("id", fileIDs).Find(&fs) if err != nil { - return nil, err + return nil, 0, 0, err } us := make(map[int64]*User) err = x.In("id", userIDs).Find(&us) if err != nil { - return nil, err + return nil, 0, 0, err } for _, r := range attachments { @@ -146,7 +148,10 @@ func (ta *TaskAttachment) ReadAll(s string, a web.Auth, page int) (interface{}, r.CreatedBy = us[r.CreatedByID] } - return attachments, err + numberOfTotalItems, err = x. + Where("task_id = ?", ta.TaskID). + Count(&TaskAttachment{}) + return attachments, len(attachments), numberOfTotalItems, err } // Delete removes an attachment diff --git a/pkg/models/task_attachment_test.go b/pkg/models/task_attachment_test.go index 31a3bcee00..b7a0628f73 100644 --- a/pkg/models/task_attachment_test.go +++ b/pkg/models/task_attachment_test.go @@ -119,7 +119,7 @@ func TestTaskAttachment_NewAttachment(t *testing.T) { func TestTaskAttachment_ReadAll(t *testing.T) { files.InitTestFileFixtures(t) ta := &TaskAttachment{TaskID: 1} - as, err := ta.ReadAll("", &User{ID: 1}, 0) + as, _, _, err := ta.ReadAll(&User{ID: 1}, "", 0, 50) attachments, _ := as.([]*TaskAttachment) assert.NoError(t, err) assert.Len(t, attachments, 3) diff --git a/pkg/models/task_readall_test.go b/pkg/models/task_readall_test.go index e87ba29e3c..47e8ce1c5e 100644 --- a/pkg/models/task_readall_test.go +++ b/pkg/models/task_readall_test.go @@ -728,7 +728,7 @@ func TestTask_ReadAll(t *testing.T) { CRUDable: tt.fields.CRUDable, Rights: tt.fields.Rights, } - got, err := lt.ReadAll(tt.args.search, tt.args.a, tt.args.page) + got, _, _, err := lt.ReadAll(tt.args.a, tt.args.search, tt.args.page, 50) if (err != nil) != tt.wantErr { t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr) return diff --git a/pkg/models/tasks.go b/pkg/models/tasks.go index 53294036e9..fb47cf5e09 100644 --- a/pkg/models/tasks.go +++ b/pkg/models/tasks.go @@ -123,7 +123,8 @@ const ( // @tags task // @Accept json // @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search tasks by task text." // @Param sort query string false "The sorting parameter. Possible values to sort by are priority, prioritydesc, priorityasc, duedate, duedatedesc, duedateasc." // @Param startdate query int false "The start date parameter to filter by. Expects a unix timestamp. If no end date, but a start date is specified, the end date is set to the current time." @@ -132,7 +133,7 @@ const ( // @Success 200 {array} models.Task "The tasks" // @Failure 500 {object} models.Message "Internal error" // @Router /tasks/all [get] -func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (t *Task) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) { var sortby SortBy switch t.Sorting { case "priority": @@ -156,6 +157,8 @@ func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error) sortby: sortby, startDate: time.Unix(t.StartDateSortUnix, 0), endDate: time.Unix(t.EndDateSortUnix, 0), + page: page, + perPage: perPage, } shareAuth, is := a.(*LinkSharing) @@ -163,15 +166,15 @@ func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error) list := &List{ID: shareAuth.ListID} err := list.GetSimpleByID() if err != nil { - return nil, err + return nil, 0, 0, err } return getTasksForLists([]*List{list}, taskopts) } // Get all lists for the user - lists, err := getRawListsForUser("", &User{ID: a.GetID()}, page) + lists, _, _, err := getRawListsForUser("", &User{ID: a.GetID()}, -1, 0) if err != nil { - return nil, err + return nil, 0, 0, err } return getTasksForLists(lists, taskopts) @@ -182,9 +185,11 @@ type taskOptions struct { sortby SortBy startDate time.Time endDate time.Time + page int + perPage int } -func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*Task, err error) { +func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*Task, resultCount int, totalItems int64, err error) { // Get all list IDs and get the tasks var listIDs []int64 @@ -219,42 +224,62 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T endDateUnix = opts.endDate.Unix() } - if err := x.In("list_id", listIDs). + err := x.In("list_id", listIDs). Where("text LIKE ?", "%"+opts.search+"%"). And("((due_date_unix BETWEEN ? AND ?) OR "+ "(start_date_unix BETWEEN ? and ?) OR "+ "(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix). OrderBy(orderby). - Find(&taskMap); err != nil { - return nil, err + Limit(getLimitFromPageIndex(opts.page, opts.perPage)). + Find(&taskMap) + if err != nil { + return nil, 0, 0, err + } + + totalItems, err = x.In("list_id", listIDs). + Where("text LIKE ?", "%"+opts.search+"%"). + And("((due_date_unix BETWEEN ? AND ?) OR "+ + "(start_date_unix BETWEEN ? and ?) OR "+ + "(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix). + Count(&Task{}) + if err != nil { + return nil, 0, 0, err } } else { - if err := x.In("list_id", listIDs). + err := x.In("list_id", listIDs). Where("text LIKE ?", "%"+opts.search+"%"). OrderBy(orderby). - Find(&taskMap); err != nil { - return nil, err + Limit(getLimitFromPageIndex(opts.page, opts.perPage)). + Find(&taskMap) + if err != nil { + return nil, 0, 0, err + } + totalItems, err = x.In("list_id", listIDs). + Where("text LIKE ?", "%"+opts.search+"%"). + Count(&Task{}) + if err != nil { + return nil, 0, 0, err } } - return + return taskMap, len(taskMap), totalItems, nil } -func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, err error) { +func getTasksForLists(lists []*List, opts *taskOptions) (tasks []*Task, resultCount int, totalItems int64, err error) { - taskMap, err := getRawTasksForLists(lists, opts) + taskMap, resultCount, totalItems, err := getRawTasksForLists(lists, opts) if err != nil { - return nil, err + return nil, 0, 0, err } tasks, err = addMoreInfoToTasks(taskMap) if err != nil { - return nil, err + return nil, 0, 0, err } // Because the list is sorted by id which we don't want (since we're dealing with maps) // we have to manually sort the tasks again here. sortTasks(tasks, opts.sortby) - return tasks, err + return tasks, resultCount, totalItems, err } func sortTasks(tasks []*Task, by SortBy) { @@ -339,7 +364,7 @@ func GetTaskByID(listTaskID int64) (listTask Task, err error) { } // Get task labels - taskLabels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{ + taskLabels, _, _, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{ TaskIDs: []int64{listTaskID}, }) if err != nil { @@ -413,7 +438,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) { } // Get all labels for all the tasks - labels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{ + labels, _, _, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{ TaskIDs: taskIDs, Page: -1, }) diff --git a/pkg/models/teams.go b/pkg/models/teams.go index e53b1efac5..0ebbc96c16 100644 --- a/pkg/models/teams.go +++ b/pkg/models/teams.go @@ -135,27 +135,37 @@ func (t *Team) ReadOne() (err error) { // @tags team // @Accept json // @Produce json -// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned." +// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page." // @Param s query string false "Search teams by its name." // @Security JWTKeyAuth // @Success 200 {array} models.Team "The teams." // @Failure 500 {object} models.Message "Internal error" // @Router /teams [get] -func (t *Team) ReadAll(search string, a web.Auth, page int) (interface{}, error) { +func (t *Team) ReadAll(a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) { if _, is := a.(*LinkSharing); is { - return nil, ErrGenericForbidden{} + return nil, 0, 0, ErrGenericForbidden{} } all := []*Team{} - err := x.Select("teams.*"). + err = x.Select("teams.*"). Table("teams"). Join("INNER", "team_members", "team_members.team_id = teams.id"). Where("team_members.user_id = ?", a.GetID()). - Limit(getLimitFromPageIndex(page)). + Limit(getLimitFromPageIndex(page, perPage)). Where("teams.name LIKE ?", "%"+search+"%"). Find(&all) + if err != nil { + return nil, 0, 0, err + } - return all, err + numberOfTotalItems, err = x. + Table("teams"). + Join("INNER", "team_members", "team_members.team_id = teams.id"). + Where("team_members.user_id = ?", a.GetID()). + Where("teams.name LIKE ?", "%"+search+"%"). + Count(&Team{}) + return all, len(all), numberOfTotalItems, err } // Create is the handler to create a team diff --git a/pkg/models/teams_test.go b/pkg/models/teams_test.go index fb96e085e9..675ed868d3 100644 --- a/pkg/models/teams_test.go +++ b/pkg/models/teams_test.go @@ -55,7 +55,7 @@ func TestTeam_Create(t *testing.T) { assert.True(t, IsErrTeamDoesNotExist(err)) // Get all teams the user is part of - ts, err := tm.ReadAll("", doer, 1) + ts, _, _, err := tm.ReadAll(doer, "", 1, 50) assert.NoError(t, err) assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice) s := reflect.ValueOf(ts) diff --git a/pkg/routes/caldav/listStorageProvider.go b/pkg/routes/caldav/listStorageProvider.go index afab6f9a96..aaa93bb30d 100644 --- a/pkg/routes/caldav/listStorageProvider.go +++ b/pkg/routes/caldav/listStorageProvider.go @@ -89,7 +89,7 @@ func (vcls *VikunjaCaldavListStorage) GetResources(rpath string, withChildren bo } // Otherwise get all lists - thelists, err := vcls.list.ReadAll("", vcls.user, -1) + thelists, _, _, err := vcls.list.ReadAll(vcls.user, "", -1, 50) if err != nil { return nil, err } diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index fa426ac33e..d7bf3e89e4 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -16,6 +16,11 @@ // @title Vikunja API // @description This is the documentation for the [Vikunja](http://vikunja.io) API. Vikunja is a cross-plattform Todo-application with a lot of features, such as sharing lists with users or teams. + +// @description # Pagination +// @description Every endpoint capable of pagination will return two headers: +// @description * `x-pagination-total-pages`: The total number of available pages for this request +// @description * `x-pagination-result-count`: The number of items returned for this request. // @description # Authorization // @description **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer `-header to authenticate successfully. // @description @@ -110,6 +115,7 @@ func NewEcho() *echo.Echo { AuthObject: apiv1.GetAuthFromClaims, }) handler.SetLoggingProvider(log.GetLogger()) + handler.SetMaxItemsPerPage(config.ServiceMaxItemsPerPage.GetInt()) return e } diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index e3a0436969..c3013fb17f 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -1,6 +1,6 @@ // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // This file was generated by swaggo/swag at -// 2019-10-14 22:46:05.9838707 +0200 CEST m=+0.157234506 +// 2019-10-23 23:00:23.451871583 +0200 CEST m=+0.120322599 package swagger @@ -75,7 +75,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -353,7 +359,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -706,7 +718,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -839,7 +857,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1230,7 +1254,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1584,7 +1614,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1855,7 +1891,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1988,7 +2030,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -2528,7 +2576,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -2773,6 +2827,18 @@ var doc = `{ "name": "id", "in": "path", "required": true + }, + { + "type": "integer", + "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", + "in": "query" } ], "responses": { @@ -3007,7 +3073,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -3424,7 +3496,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -3606,7 +3684,13 @@ var doc = `{ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -5245,7 +5329,7 @@ var doc = `{ "type": "boolean" }, "max_file_size": { - "type": "integer" + "type": "string" }, "motd": { "type": "string" @@ -5284,7 +5368,7 @@ var SwaggerInfo = swaggerInfo{ BasePath: "/api/v1", Schemes: []string{}, Title: "Vikunja API", - Description: "This is the documentation for the [Vikunja](http://vikunja.io) API. Vikunja is a cross-plattform Todo-application with a lot of features, such as sharing lists with users or teams. \n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer `-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n", + Description: "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer `-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n", } type s struct{} diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index cd7aa39841..88d2dd2c45 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -1,7 +1,7 @@ { "swagger": "2.0", "info": { - "description": "This is the documentation for the [Vikunja](http://vikunja.io) API. Vikunja is a cross-plattform Todo-application with a lot of features, such as sharing lists with users or teams. \u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer \u003cjwt-token\u003e`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e", + "description": "# Pagination\nEvery endpoint capable of pagination will return two headers:\n* `x-pagination-total-pages`: The total number of available pages for this request\n* `x-pagination-result-count`: The number of items returned for this request.\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer \u003cjwt-token\u003e`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n\u003c!-- ReDoc-Inject: \u003csecurity-definitions\u003e --\u003e", "title": "Vikunja API", "contact": { "name": "General Vikunja contact", @@ -57,7 +57,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -335,7 +341,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -688,7 +700,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -821,7 +839,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1212,7 +1236,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1566,7 +1596,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1837,7 +1873,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -1970,7 +2012,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -2510,7 +2558,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -2755,6 +2809,18 @@ "name": "id", "in": "path", "required": true + }, + { + "type": "integer", + "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", + "in": "query" } ], "responses": { @@ -2989,7 +3055,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -3406,7 +3478,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -3588,7 +3666,13 @@ { "type": "integer", "description": "The page number. Used for pagination. If not provided, the first page of results is returned.", - "name": "p", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page.", + "name": "per_page", "in": "query" }, { @@ -5226,7 +5310,7 @@ "type": "boolean" }, "max_file_size": { - "type": "integer" + "type": "string" }, "motd": { "type": "string" diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 827514afd0..4e82864b40 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -833,7 +833,7 @@ definitions: link_sharing_enabled: type: boolean max_file_size: - type: integer + type: string motd: type: string version: @@ -845,7 +845,10 @@ info: name: General Vikunja contact url: http://vikunja.io/en/contact/ description: |- - This is the documentation for the [Vikunja](http://vikunja.io) API. Vikunja is a cross-plattform Todo-application with a lot of features, such as sharing lists with users or teams. + # Pagination + Every endpoint capable of pagination will return two headers: + * `x-pagination-total-pages`: The total number of available pages for this request + * `x-pagination-result-count`: The number of items returned for this request. # Authorization **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer `-header to authenticate successfully. @@ -880,7 +883,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search labels by label text. in: query @@ -1061,7 +1069,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search lists by title. in: query @@ -1290,7 +1303,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search teams by its name. in: query @@ -1377,7 +1395,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search users by its name. in: query @@ -1464,7 +1487,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search shares by hash. in: query @@ -1868,7 +1896,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search namespaces by name. in: query @@ -2043,7 +2076,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search teams by its name. in: query @@ -2131,7 +2169,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search users by its name. in: query @@ -2567,6 +2610,16 @@ paths: name: id required: true type: integer + - description: The page number. Used for pagination. If not provided, the first + page of results is returned. + in: query + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page + type: integer produces: - application/json responses: @@ -2724,7 +2777,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search labels by label text. in: query @@ -2844,7 +2902,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search assignees by their username. in: query @@ -3119,7 +3182,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search tasks by task text. in: query @@ -3209,7 +3277,12 @@ paths: - description: The page number. Used for pagination. If not provided, the first page of results is returned. in: query - name: p + name: page + type: integer + - description: The maximum number of items per page. Note this parameter is + limited by the configured maximum of items per page. + in: query + name: per_page type: integer - description: Search teams by its name. in: query diff --git a/vendor/code.vikunja.io/web/Readme.md b/vendor/code.vikunja.io/web/Readme.md index a4a00a35f2..d82ad9e36c 100644 --- a/vendor/code.vikunja.io/web/Readme.md +++ b/vendor/code.vikunja.io/web/Readme.md @@ -60,7 +60,7 @@ This interface defines methods to Create/Read/ReadAll/Update/Delete something. I type CRUDable interface { Create(Auth) error ReadOne() error - ReadAll(string, Auth, int) (interface{}, error) + ReadAll(auth Auth, search string, page int64, perPage int64) (result interface{}, resultCount int64, numberOfPages int64, err error) Update() error Delete() error } @@ -122,6 +122,13 @@ You can provide your own instance of `logger.Logger` (using [go-logging](https:/ It will use this instance to log errors which are not better specified or things like users trying to do something they're not allowed to do and so on. +#### MaxItemsPerPage + +Contains the maximum number of items per page. +If the client requests more items than this, the number of items requested is set to this value. + +See [pagination](#pagination) for more. + #### Full Example ```go @@ -137,18 +144,35 @@ handler.SetLoggingProvider(&log.Log) ### Pagination -When using the `ReadAll`-method, the third parameter contains the requested page. -Your function should return only the number of results corresponding to that page. -The number of items per page should be set by your application elewhere, most likely in its config. +The `ReadAll`-method has a number of parameters: -The number of items to return is then usually calculated with some method like `page_number * items_per_page`. +```go +ReadAll(auth Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfItems int64, err error) +``` + +The third parameter contains the requested page, the fourth parameter contains the number of items per page. +You should calculate the limits accordingly. + +If the number of items per page are not set by the client, the web handler will pass the maximum number of items per page instead. +This makes items per page optional for clients. +Take a look at [the config section](#handler-config) for information on how to set that value. + +You need to return a number of things: + +* The result itself, usually a slice +* The number of items you return in `result`. Most of the time, this is just `len(result)`. You need to return this value to make the clients aware if they requested a number of items > max items per page. +* The total number of items available. We use the total number of items here and not the number pages so the implementations don't have to deal with calculating the number of pages from that. The total number of clients is then calculated and returned to the client, ite can then be used by the clients to build client-side pagination or similar. +* An error. + +The number of items and the total number of pages available will be returned in the `x-pagination-total-pages` and `x-pagination-result-count` response headers. +_You should put this in your api documentation._ ### Search When using the `ReadAll`-method, the first parameter is a search term which should be used to search items of your struct. You define the critera inside of that function. -Users can then pass the `?s=something` parameter to the url to search, thats something you should put in your api documentation. +Users can then pass the `?s=something` parameter to the url to search, _thats something you should put in your api documentation_. As the logic for "give me everything" and "give me everything where the name contains 'something'" is mostly the same, we made the decision to design the function like this, in order to keep the places with mostly the same logic as few as possible. diff --git a/vendor/code.vikunja.io/web/handler/config.go b/vendor/code.vikunja.io/web/handler/config.go index c568e99069..b961138a37 100644 --- a/vendor/code.vikunja.io/web/handler/config.go +++ b/vendor/code.vikunja.io/web/handler/config.go @@ -21,9 +21,11 @@ import ( "github.com/op/go-logging" ) +// Config contains the config for the web handler type Config struct { AuthProvider *web.Auths LoggingProvider *logging.Logger + MaxItemsPerPage int } var config *Config @@ -32,10 +34,17 @@ func init() { config = &Config{} } +// SetAuthProvider sets the auth provider in config func SetAuthProvider(provider *web.Auths) { config.AuthProvider = provider } +// SetLoggingProvider sets the logging provider in the config func SetLoggingProvider(logger *logging.Logger) { config.LoggingProvider = logger } + +// SetMaxItemsPerPage sets the max number of items per page in the config +func SetMaxItemsPerPage(maxItemsPerPage int) { + config.MaxItemsPerPage = maxItemsPerPage +} diff --git a/vendor/code.vikunja.io/web/handler/read_all.go b/vendor/code.vikunja.io/web/handler/read_all.go index 1fd5db7812..001b60f0ed 100644 --- a/vendor/code.vikunja.io/web/handler/read_all.go +++ b/vendor/code.vikunja.io/web/handler/read_all.go @@ -17,6 +17,7 @@ package handler import ( "github.com/labstack/echo/v4" + "math" "net/http" "strconv" ) @@ -47,16 +48,56 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.") } if pageNumber < 0 { - return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.") + return echo.NewHTTPError(http.StatusBadRequest, "Page number cannot be negative.") + } + + // Items per page + var perPageNumber int + perPage := ctx.QueryParam("per_page") + // If we dont have an "items per page" parameter, we want to use the default. + // To prevent Atoi from failing, we check this here. + if perPage != "" { + perPageNumber, err = strconv.Atoi(perPage) + if err != nil { + config.LoggingProvider.Error(err.Error()) + return echo.NewHTTPError(http.StatusBadRequest, "Bad per page amount requested.") + } + } + // Set default page count + if perPageNumber == 0 { + perPageNumber = config.MaxItemsPerPage + } + if perPageNumber < 1 { + return echo.NewHTTPError(http.StatusBadRequest, "Per page amount cannot be negative.") + } + if perPageNumber > config.MaxItemsPerPage { + perPageNumber = config.MaxItemsPerPage } // Search search := ctx.QueryParam("s") - lists, err := currentStruct.ReadAll(search, currentAuth, pageNumber) + result, resultCount, numberOfItems, err := currentStruct.ReadAll(currentAuth, search, pageNumber, perPageNumber) if err != nil { return HandleHTTPError(err, ctx) } - return ctx.JSON(http.StatusOK, lists) + // Calculate the number of pages from the number of items + // We always round up, because if we don't have a number of items which is exactly dividable by the number of items per page, + // we would get a result that is one page off. + var numberOfPages = math.Ceil(float64(numberOfItems) / float64(perPageNumber)) + // If we return all results, we only have one page + if pageNumber < 0 { + numberOfPages = 1 + } + // If we don't have results, we don't have a page + if resultCount == 0 { + numberOfPages = 0 + } + + ctx.Response().Header().Set("x-pagination-total-pages", strconv.FormatFloat(numberOfPages, 'f', 0, 64)) + ctx.Response().Header().Set("x-pagination-result-count", strconv.FormatInt(int64(resultCount), 10)) + ctx.Response().Header().Set("Access-Control-Expose-Headers", "x-pagination-total-pages, x-pagination-result-count") + + return ctx.JSON(http.StatusOK, result) } diff --git a/vendor/code.vikunja.io/web/web.go b/vendor/code.vikunja.io/web/web.go index 868c367a05..0b92cc102d 100644 --- a/vendor/code.vikunja.io/web/web.go +++ b/vendor/code.vikunja.io/web/web.go @@ -31,7 +31,7 @@ type Rights interface { type CRUDable interface { Create(Auth) error ReadOne() error - ReadAll(string, Auth, int) (interface{}, error) + ReadAll(auth Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) Update() error Delete() error } diff --git a/vendor/github.com/labstack/echo/v4/bind.go b/vendor/github.com/labstack/echo/v4/bind.go index d128083b61..ef04eaaf33 100644 --- a/vendor/github.com/labstack/echo/v4/bind.go +++ b/vendor/github.com/labstack/echo/v4/bind.go @@ -43,15 +43,11 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { if err := b.bindData(i, params, "param"); err != nil { return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) } - + if err = b.bindData(i, c.QueryParams(), "query"); err != nil { + return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) + } if req.ContentLength == 0 { - if req.Method == http.MethodGet || req.Method == http.MethodDelete { - if err = b.bindData(i, c.QueryParams(), "query"); err != nil { - return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) - } - return - } - return NewHTTPError(http.StatusBadRequest, "Request body can't be empty") + return } ctype := req.Header.Get(HeaderContentType) switch { @@ -88,12 +84,19 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) { } func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag string) error { - if len(data) == 0 { + if ptr == nil || len(data) == 0 { return nil } typ := reflect.TypeOf(ptr).Elem() val := reflect.ValueOf(ptr).Elem() + if m, ok := ptr.(*map[string]interface{}); ok { + for k, v := range data { + (*m)[k] = v[0] + } + return nil + } + if typ.Kind() != reflect.Struct { return errors.New("binding element must be a struct") } diff --git a/vendor/github.com/labstack/echo/v4/context.go b/vendor/github.com/labstack/echo/v4/context.go index 065f5815a7..e0f4cc00f4 100644 --- a/vendor/github.com/labstack/echo/v4/context.go +++ b/vendor/github.com/labstack/echo/v4/context.go @@ -26,6 +26,9 @@ type ( // SetRequest sets `*http.Request`. SetRequest(r *http.Request) + // SetResponse sets `*Response`. + SetResponse(r *Response) + // Response returns `*Response`. Response() *Response @@ -228,6 +231,10 @@ func (c *context) Response() *Response { return c.response } +func (c *context) SetResponse(r *Response) { + c.response = r +} + func (c *context) IsTLS() bool { return c.request.TLS != nil } diff --git a/vendor/github.com/labstack/echo/v4/echo.go b/vendor/github.com/labstack/echo/v4/echo.go index 56b2cf8c76..459c89882a 100644 --- a/vendor/github.com/labstack/echo/v4/echo.go +++ b/vendor/github.com/labstack/echo/v4/echo.go @@ -99,9 +99,9 @@ type ( // HTTPError represents an error that occurred while handling a request. HTTPError struct { - Code int - Message interface{} - Internal error // Stores the error returned by an external dependency + Code int `json:"-"` + Message interface{} `json:"message"` + Internal error `json:"-"` // Stores the error returned by an external dependency } // MiddlewareFunc defines a function to process middleware. @@ -222,11 +222,12 @@ const ( HeaderContentSecurityPolicy = "Content-Security-Policy" HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" HeaderXCSRFToken = "X-CSRF-Token" + HeaderReferrerPolicy = "Referrer-Policy" ) const ( // Version of Echo - Version = "4.1.5" + Version = "4.1.11" website = "https://echo.labstack.com" // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo banner = ` @@ -340,32 +341,31 @@ func (e *Echo) Routers() map[string]*Router { // DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response // with status code. func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { - var ( - code = http.StatusInternalServerError - msg interface{} - ) - - if he, ok := err.(*HTTPError); ok { - code = he.Code - msg = he.Message + he, ok := err.(*HTTPError) + if ok { if he.Internal != nil { - err = fmt.Errorf("%v, %v", err, he.Internal) + if herr, ok := he.Internal.(*HTTPError); ok { + he = herr + } } - } else if e.Debug { - msg = err.Error() } else { - msg = http.StatusText(code) + he = &HTTPError{ + Code: http.StatusInternalServerError, + Message: http.StatusText(http.StatusInternalServerError), + } } - if _, ok := msg.(string); ok { - msg = Map{"message": msg} + if e.Debug { + he.Message = err.Error() + } else if m, ok := he.Message.(string); ok { + he.Message = Map{"message": m} } // Send response if !c.Response().Committed { if c.Request().Method == http.MethodHead { // Issue #608 - err = c.NoContent(code) + err = c.NoContent(he.Code) } else { - err = c.JSON(code, msg) + err = c.JSON(he.Code, he.Message) } if err != nil { e.Logger.Error(err) @@ -748,7 +748,7 @@ func NewHTTPError(code int, message ...interface{}) *HTTPError { // Error makes it compatible with `error` interface. func (he *HTTPError) Error() string { - return fmt.Sprintf("code=%d, message=%v", he.Code, he.Message) + return fmt.Sprintf("code=%d, message=%v, internal=%v", he.Code, he.Message, he.Internal) } // SetInternal sets error to HTTPError.Internal @@ -771,6 +771,7 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc { return func(c Context) (err error) { m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { c.SetRequest(r) + c.SetResponse(NewResponse(w, c.Echo())) err = next(c) })).ServeHTTP(c.Response(), c.Request()) return diff --git a/vendor/github.com/labstack/echo/v4/go.mod b/vendor/github.com/labstack/echo/v4/go.mod index d76b9843ec..f088ba6e4d 100644 --- a/vendor/github.com/labstack/echo/v4/go.mod +++ b/vendor/github.com/labstack/echo/v4/go.mod @@ -4,12 +4,8 @@ go 1.12 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/labstack/gommon v0.2.9 - github.com/stretchr/testify v1.3.0 + github.com/labstack/gommon v0.3.0 + github.com/stretchr/testify v1.4.0 github.com/valyala/fasttemplate v1.0.1 - golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 - golang.org/x/net v0.0.0-20190607181551-461777fb6f67 // indirect - golang.org/x/sys v0.0.0-20190609082536-301114b31cce // indirect - golang.org/x/text v0.3.2 // indirect - golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3 // indirect + golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 ) diff --git a/vendor/github.com/labstack/echo/v4/go.sum b/vendor/github.com/labstack/echo/v4/go.sum index 695f92764a..5aedb2e223 100644 --- a/vendor/github.com/labstack/echo/v4/go.sum +++ b/vendor/github.com/labstack/echo/v4/go.sum @@ -1,55 +1,34 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0= -github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= -github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU= -github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= -github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= -golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190607181551-461777fb6f67 h1:rJJxsykSlULwd2P2+pg/rtnwN2FrWp4IuCxOSyS0V00= -golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2 h1:T5DasATyLQfmbTpfEXx/IOL9vfjzW6up+ZDkmHvIf2s= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190609082536-301114b31cce h1:CQakrGkKbydnUmt7cFIlmQ4lNQiqdTPt6xzXij4nYCc= -golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/labstack/echo/v4/middleware/logger.go b/vendor/github.com/labstack/echo/v4/middleware/logger.go index 6fd59efbc5..9baac47695 100644 --- a/vendor/github.com/labstack/echo/v4/middleware/logger.go +++ b/vendor/github.com/labstack/echo/v4/middleware/logger.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/json" "io" - "os" "strconv" "strings" "sync" @@ -74,7 +73,6 @@ var ( `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", CustomTimeFormat: "2006-01-02 15:04:05.00000", - Output: os.Stdout, colorer: color.New(), } ) @@ -214,6 +212,10 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc { return } + if config.Output == nil { + _, err = c.Logger().Output().Write(buf.Bytes()) + return + } _, err = config.Output.Write(buf.Bytes()) return } diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy.go b/vendor/github.com/labstack/echo/v4/middleware/proxy.go index 532346d56e..ef5602bd68 100644 --- a/vendor/github.com/labstack/echo/v4/middleware/proxy.go +++ b/vendor/github.com/labstack/echo/v4/middleware/proxy.go @@ -92,15 +92,14 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { in, _, err := c.Response().Hijack() if err != nil { - c.Error(fmt.Errorf("proxy raw, hijack error=%v, url=%s", t.URL, err)) + c.Set("_error", fmt.Sprintf("proxy raw, hijack error=%v, url=%s", t.URL, err)) return } defer in.Close() out, err := net.Dial("tcp", t.URL.Host) if err != nil { - he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err)) - c.Error(he) + c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err))) return } defer out.Close() @@ -108,8 +107,7 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { // Write header err = r.Write(out) if err != nil { - he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err)) - c.Error(he) + c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err))) return } @@ -123,7 +121,7 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler { go cp(in, out) err = <-errCh if err != nil && err != io.EOF { - c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err) + c.Set("_error", fmt.Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err)) } }) } @@ -251,6 +249,9 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc { default: proxyHTTP(tgt, c, config).ServeHTTP(res, req) } + if e, ok := c.Get("_error").(error); ok { + err = e + } return } diff --git a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go index 7784f9c69e..12b7568bf8 100644 --- a/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go +++ b/vendor/github.com/labstack/echo/v4/middleware/proxy_1_11.go @@ -17,8 +17,7 @@ func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handle if tgt.Name != "" { desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String()) } - c.Logger().Errorf("remote %s unreachable, could not forward: %v", desc, err) - c.Error(echo.NewHTTPError(http.StatusServiceUnavailable)) + c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err))) } proxy.Transport = config.Transport return proxy diff --git a/vendor/github.com/labstack/echo/v4/middleware/secure.go b/vendor/github.com/labstack/echo/v4/middleware/secure.go index 77a1487fe8..6c4051723e 100644 --- a/vendor/github.com/labstack/echo/v4/middleware/secure.go +++ b/vendor/github.com/labstack/echo/v4/middleware/secure.go @@ -66,6 +66,11 @@ type ( // maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/ // Optional. Default value false. HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"` + + // ReferrerPolicy sets the `Referrer-Policy` header providing security against + // leaking potentially sensitive request paths to third parties. + // Optional. Default value "". + ReferrerPolicy string `yaml:"referrer_policy"` } ) @@ -131,6 +136,9 @@ func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc { res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy) } } + if config.ReferrerPolicy != "" { + res.Header().Set(echo.HeaderReferrerPolicy, config.ReferrerPolicy) + } return next(c) } } diff --git a/vendor/github.com/labstack/echo/v4/router.go b/vendor/github.com/labstack/echo/v4/router.go index 8d3a01802e..70bf409f97 100644 --- a/vendor/github.com/labstack/echo/v4/router.go +++ b/vendor/github.com/labstack/echo/v4/router.go @@ -20,8 +20,8 @@ type ( pnames []string methodHandler *methodHandler } - kind uint8 - children []*node + kind uint8 + children []*node methodHandler struct { connect HandlerFunc delete HandlerFunc @@ -336,10 +336,14 @@ func (r *Router) Find(method, path string, c Context) { } } + if l == pl { // Continue search search = search[l:] } else { + if nn == nil { // Issue #1348 + return // Not found + } cn = nn search = ns if nk == pkind { @@ -347,8 +351,6 @@ func (r *Router) Find(method, path string, c Context) { } else if nk == akind { goto Any } - // Not found - return } if search == "" { @@ -398,6 +400,9 @@ func (r *Router) Find(method, path string, c Context) { if nn != nil { cn = nn nn = cn.parent // Next (Issue #954) + if nn != nil { + nk = nn.kind + } search = ns if nk == pkind { goto Param @@ -405,8 +410,7 @@ func (r *Router) Find(method, path string, c Context) { goto Any } } - // Not found - return + return // Not found } pvalues[len(cn.pnames)-1] = search break diff --git a/vendor/github.com/labstack/gommon/bytes/bytes.go b/vendor/github.com/labstack/gommon/bytes/bytes.go index 232c8d2537..2f6bcec6e7 100644 --- a/vendor/github.com/labstack/gommon/bytes/bytes.go +++ b/vendor/github.com/labstack/gommon/bytes/bytes.go @@ -4,6 +4,7 @@ import ( "fmt" "regexp" "strconv" + "strings" ) type ( @@ -73,7 +74,7 @@ func (*Bytes) Parse(value string) (i int64, err error) { return 0, fmt.Errorf("error parsing value=%s", value) } bytesString := parts[1] - multiple := parts[2] + multiple := strings.ToUpper(parts[2]) bytes, err := strconv.ParseFloat(bytesString, 64) if err != nil { return diff --git a/vendor/github.com/mattn/go-colorable/colorable_appengine.go b/vendor/github.com/mattn/go-colorable/colorable_appengine.go index 1f28d773d7..0b0aef8370 100644 --- a/vendor/github.com/mattn/go-colorable/colorable_appengine.go +++ b/vendor/github.com/mattn/go-colorable/colorable_appengine.go @@ -9,7 +9,7 @@ import ( _ "github.com/mattn/go-isatty" ) -// NewColorable return new instance of Writer which handle escape sequence. +// NewColorable returns new instance of Writer which handles escape sequence. func NewColorable(file *os.File) io.Writer { if file == nil { panic("nil passed instead of *os.File to NewColorable()") @@ -18,12 +18,12 @@ func NewColorable(file *os.File) io.Writer { return file } -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout. func NewColorableStdout() io.Writer { return os.Stdout } -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr. func NewColorableStderr() io.Writer { return os.Stderr } diff --git a/vendor/github.com/mattn/go-colorable/colorable_others.go b/vendor/github.com/mattn/go-colorable/colorable_others.go index 887f203dc7..3fb771dcca 100644 --- a/vendor/github.com/mattn/go-colorable/colorable_others.go +++ b/vendor/github.com/mattn/go-colorable/colorable_others.go @@ -10,7 +10,7 @@ import ( _ "github.com/mattn/go-isatty" ) -// NewColorable return new instance of Writer which handle escape sequence. +// NewColorable returns new instance of Writer which handles escape sequence. func NewColorable(file *os.File) io.Writer { if file == nil { panic("nil passed instead of *os.File to NewColorable()") @@ -19,12 +19,12 @@ func NewColorable(file *os.File) io.Writer { return file } -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout. func NewColorableStdout() io.Writer { return os.Stdout } -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr. func NewColorableStderr() io.Writer { return os.Stderr } diff --git a/vendor/github.com/mattn/go-colorable/colorable_windows.go b/vendor/github.com/mattn/go-colorable/colorable_windows.go index 404e10ca02..1bd628f25c 100644 --- a/vendor/github.com/mattn/go-colorable/colorable_windows.go +++ b/vendor/github.com/mattn/go-colorable/colorable_windows.go @@ -81,7 +81,7 @@ var ( procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") ) -// Writer provide colorable Writer to the console +// Writer provides colorable Writer to the console type Writer struct { out io.Writer handle syscall.Handle @@ -91,7 +91,7 @@ type Writer struct { rest bytes.Buffer } -// NewColorable return new instance of Writer which handle escape sequence from File. +// NewColorable returns new instance of Writer which handles escape sequence from File. func NewColorable(file *os.File) io.Writer { if file == nil { panic("nil passed instead of *os.File to NewColorable()") @@ -106,12 +106,12 @@ func NewColorable(file *os.File) io.Writer { return file } -// NewColorableStdout return new instance of Writer which handle escape sequence for stdout. +// NewColorableStdout returns new instance of Writer which handles escape sequence for stdout. func NewColorableStdout() io.Writer { return NewColorable(os.Stdout) } -// NewColorableStderr return new instance of Writer which handle escape sequence for stderr. +// NewColorableStderr returns new instance of Writer which handles escape sequence for stderr. func NewColorableStderr() io.Writer { return NewColorable(os.Stderr) } @@ -414,7 +414,15 @@ func doTitleSequence(er *bytes.Reader) error { return nil } -// Write write data on console +// returns Atoi(s) unless s == "" in which case it returns def +func atoiWithDefault(s string, def int) (int, error) { + if s == "" { + return def, nil + } + return strconv.Atoi(s) +} + +// Write writes data on console func (w *Writer) Write(data []byte) (n int, err error) { var csbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) @@ -500,7 +508,7 @@ loop: switch m { case 'A': - n, err = strconv.Atoi(buf.String()) + n, err = atoiWithDefault(buf.String(), 1) if err != nil { continue } @@ -508,7 +516,7 @@ loop: csbi.cursorPosition.y -= short(n) procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'B': - n, err = strconv.Atoi(buf.String()) + n, err = atoiWithDefault(buf.String(), 1) if err != nil { continue } @@ -516,7 +524,7 @@ loop: csbi.cursorPosition.y += short(n) procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'C': - n, err = strconv.Atoi(buf.String()) + n, err = atoiWithDefault(buf.String(), 1) if err != nil { continue } @@ -524,7 +532,7 @@ loop: csbi.cursorPosition.x += short(n) procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) case 'D': - n, err = strconv.Atoi(buf.String()) + n, err = atoiWithDefault(buf.String(), 1) if err != nil { continue } @@ -557,6 +565,9 @@ loop: if err != nil { continue } + if n < 1 { + n = 1 + } procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) csbi.cursorPosition.x = short(n - 1) procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) @@ -635,6 +646,20 @@ loop: } procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + case 'X': + n := 0 + if buf.Len() > 0 { + n, err = strconv.Atoi(buf.String()) + if err != nil { + continue + } + } + procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) + var cursor coord + var written dword + cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} + procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) + procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) case 'm': procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) attr := csbi.attributes diff --git a/vendor/github.com/mattn/go-colorable/go.sum b/vendor/github.com/mattn/go-colorable/go.sum index a53c2ca7de..2c12960ec7 100644 --- a/vendor/github.com/mattn/go-colorable/go.sum +++ b/vendor/github.com/mattn/go-colorable/go.sum @@ -1,6 +1,4 @@ github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/vendor/github.com/mattn/go-colorable/noncolorable.go b/vendor/github.com/mattn/go-colorable/noncolorable.go index 9721e16f4b..95f2c6be25 100644 --- a/vendor/github.com/mattn/go-colorable/noncolorable.go +++ b/vendor/github.com/mattn/go-colorable/noncolorable.go @@ -5,17 +5,17 @@ import ( "io" ) -// NonColorable hold writer but remove escape sequence. +// NonColorable holds writer but removes escape sequence. type NonColorable struct { out io.Writer } -// NewNonColorable return new instance of Writer which remove escape sequence from Writer. +// NewNonColorable returns new instance of Writer which removes escape sequence from Writer. func NewNonColorable(w io.Writer) io.Writer { return &NonColorable{out: w} } -// Write write data on console +// Write writes data on console func (w *NonColorable) Write(data []byte) (n int, err error) { er := bytes.NewReader(data) var bw [1]byte diff --git a/vendor/github.com/valyala/fasttemplate/README.md b/vendor/github.com/valyala/fasttemplate/README.md index 3a4d56cee3..2839ed0f7a 100644 --- a/vendor/github.com/valyala/fasttemplate/README.md +++ b/vendor/github.com/valyala/fasttemplate/README.md @@ -3,7 +3,7 @@ fasttemplate Simple and fast template engine for Go. -Fasttemplate peforms only a single task - it substitutes template placeholders +Fasttemplate performs only a single task - it substitutes template placeholders with user-defined values. At high speed :) Take a look at [quicktemplate](https://github.com/valyala/quicktemplate) if you need fast yet powerful html template engine. diff --git a/vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go b/vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go new file mode 100644 index 0000000000..5144deeccd --- /dev/null +++ b/vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package unix + +// Round the length of a raw sockaddr up to align it properly. +func cmsgAlignOf(salen int) int { + salign := SizeofPtr + if SizeofPtr == 8 && !supportsABI(_dragonflyABIChangeVersion) { + // 64-bit Dragonfly before the September 2019 ABI changes still requires + // 32-bit aligned access to network subsystem. + salign = 4 + } + return (salen + salign - 1) & ^(salign - 1) +} diff --git a/vendor/golang.org/x/sys/unix/sockcmsg_linux.go b/vendor/golang.org/x/sys/unix/sockcmsg_linux.go index 6079eb4ac1..8bf4570594 100644 --- a/vendor/golang.org/x/sys/unix/sockcmsg_linux.go +++ b/vendor/golang.org/x/sys/unix/sockcmsg_linux.go @@ -17,7 +17,7 @@ func UnixCredentials(ucred *Ucred) []byte { h.Level = SOL_SOCKET h.Type = SCM_CREDENTIALS h.SetLen(CmsgLen(SizeofUcred)) - *((*Ucred)(cmsgData(h))) = *ucred + *(*Ucred)(h.data(0)) = *ucred return b } diff --git a/vendor/golang.org/x/sys/unix/sockcmsg_unix.go b/vendor/golang.org/x/sys/unix/sockcmsg_unix.go index 062bcabab1..003916ed7a 100644 --- a/vendor/golang.org/x/sys/unix/sockcmsg_unix.go +++ b/vendor/golang.org/x/sys/unix/sockcmsg_unix.go @@ -9,35 +9,9 @@ package unix import ( - "runtime" "unsafe" ) -// Round the length of a raw sockaddr up to align it properly. -func cmsgAlignOf(salen int) int { - salign := SizeofPtr - - switch runtime.GOOS { - case "aix": - // There is no alignment on AIX. - salign = 1 - case "darwin", "dragonfly", "solaris", "illumos": - // NOTE: It seems like 64-bit Darwin, DragonFly BSD, - // illumos, and Solaris kernels still require 32-bit - // aligned access to network subsystem. - if SizeofPtr == 8 { - salign = 4 - } - case "netbsd", "openbsd": - // NetBSD and OpenBSD armv7 require 64-bit alignment. - if runtime.GOARCH == "arm" { - salign = 8 - } - } - - return (salen + salign - 1) & ^(salign - 1) -} - // CmsgLen returns the value to store in the Len field of the Cmsghdr // structure, taking into account any necessary alignment. func CmsgLen(datalen int) int { @@ -50,8 +24,8 @@ func CmsgSpace(datalen int) int { return cmsgAlignOf(SizeofCmsghdr) + cmsgAlignOf(datalen) } -func cmsgData(h *Cmsghdr) unsafe.Pointer { - return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(SizeofCmsghdr))) +func (h *Cmsghdr) data(offset uintptr) unsafe.Pointer { + return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(SizeofCmsghdr)) + offset) } // SocketControlMessage represents a socket control message. @@ -94,10 +68,8 @@ func UnixRights(fds ...int) []byte { h.Level = SOL_SOCKET h.Type = SCM_RIGHTS h.SetLen(CmsgLen(datalen)) - data := cmsgData(h) - for _, fd := range fds { - *(*int32)(data) = int32(fd) - data = unsafe.Pointer(uintptr(data) + 4) + for i, fd := range fds { + *(*int32)(h.data(4 * uintptr(i))) = int32(fd) } return b } diff --git a/vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go b/vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go new file mode 100644 index 0000000000..7d08dae5ba --- /dev/null +++ b/vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go @@ -0,0 +1,38 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build aix darwin freebsd linux netbsd openbsd solaris + +package unix + +import ( + "runtime" +) + +// Round the length of a raw sockaddr up to align it properly. +func cmsgAlignOf(salen int) int { + salign := SizeofPtr + + // dragonfly needs to check ABI version at runtime, see cmsgAlignOf in + // sockcmsg_dragonfly.go + switch runtime.GOOS { + case "aix": + // There is no alignment on AIX. + salign = 1 + case "darwin", "illumos", "solaris": + // NOTE: It seems like 64-bit Darwin, Illumos and Solaris + // kernels still require 32-bit aligned access to network + // subsystem. + if SizeofPtr == 8 { + salign = 4 + } + case "netbsd", "openbsd": + // NetBSD and OpenBSD armv7 require 64-bit alignment. + if runtime.GOARCH == "arm" { + salign = 8 + } + } + + return (salen + salign - 1) & ^(salign - 1) +} diff --git a/vendor/golang.org/x/sys/unix/syscall_bsd.go b/vendor/golang.org/x/sys/unix/syscall_bsd.go index 3e6671426c..d52bcc41c3 100644 --- a/vendor/golang.org/x/sys/unix/syscall_bsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_bsd.go @@ -237,7 +237,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { break } } - bytes := (*[10000]byte)(unsafe.Pointer(&pp.Path[0]))[0:n] + bytes := (*[len(pp.Path)]byte)(unsafe.Pointer(&pp.Path[0]))[0:n] sa.Name = string(bytes) return sa, nil diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin.go b/vendor/golang.org/x/sys/unix/syscall_darwin.go index c5018a385c..0a1cc74b3e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin.go @@ -339,6 +339,8 @@ func Kill(pid int, signum syscall.Signal) (err error) { return kill(pid, int(sig //sys ioctl(fd int, req uint, arg uintptr) (err error) +//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS_SYSCTL + func Uname(uname *Utsname) error { mib := []_C_int{CTL_KERN, KERN_OSTYPE} n := unsafe.Sizeof(uname.Sysname) diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_386.go b/vendor/golang.org/x/sys/unix/syscall_darwin_386.go index dd756e708c..707ba4f59a 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin_386.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin_386.go @@ -10,7 +10,6 @@ import ( "syscall" ) -//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL //sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error) func setTimespec(sec, nsec int64) Timespec { diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go index 7f148c428e..fdbfb5911a 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go @@ -10,7 +10,6 @@ import ( "syscall" ) -//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL //sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error) func setTimespec(sec, nsec int64) Timespec { diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_arm.go b/vendor/golang.org/x/sys/unix/syscall_darwin_arm.go index 58be02e718..f8bc4cfb1f 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin_arm.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin_arm.go @@ -12,10 +12,6 @@ func ptrace(request int, pid int, addr uintptr, data uintptr) error { return ENOTSUP } -func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error { - return ENOTSUP -} - func setTimespec(sec, nsec int64) Timespec { return Timespec{Sec: int32(sec), Nsec: int32(nsec)} } diff --git a/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go index 1ee931f97c..5ede3ac316 100644 --- a/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go @@ -14,10 +14,6 @@ func ptrace(request int, pid int, addr uintptr, data uintptr) error { return ENOTSUP } -func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error { - return ENOTSUP -} - func setTimespec(sec, nsec int64) Timespec { return Timespec{Sec: sec, Nsec: nsec} } diff --git a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go index 8c8d502979..8a195ae586 100644 --- a/vendor/golang.org/x/sys/unix/syscall_dragonfly.go +++ b/vendor/golang.org/x/sys/unix/syscall_dragonfly.go @@ -12,9 +12,25 @@ package unix -import "unsafe" +import ( + "sync" + "unsafe" +) -//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL +// See version list in https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/param.h +var ( + osreldateOnce sync.Once + osreldate uint32 +) + +// First __DragonFly_version after September 2019 ABI changes +// http://lists.dragonflybsd.org/pipermail/users/2019-September/358280.html +const _dragonflyABIChangeVersion = 500705 + +func supportsABI(ver uint32) bool { + osreldateOnce.Do(func() { osreldate, _ = SysctlUint32("kern.osreldate") }) + return osreldate >= ver +} // SockaddrDatalink implements the Sockaddr interface for AF_LINK type sockets. type SockaddrDatalink struct { @@ -152,6 +168,8 @@ func setattrlistTimes(path string, times []Timespec, flags int) error { //sys ioctl(fd int, req uint, arg uintptr) (err error) +//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL + func sysctlUname(mib []_C_int, old *byte, oldlen *uintptr) error { err := sysctl(mib, old, oldlen, nil, 0) if err != nil { diff --git a/vendor/golang.org/x/sys/unix/syscall_freebsd.go b/vendor/golang.org/x/sys/unix/syscall_freebsd.go index 25ac9340c0..79d522215e 100644 --- a/vendor/golang.org/x/sys/unix/syscall_freebsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_freebsd.go @@ -36,8 +36,6 @@ var ( // INO64_FIRST from /usr/src/lib/libc/sys/compat-ino64.h const _ino64First = 1200031 -//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL - func supportsABI(ver uint32) bool { osreldateOnce.Do(func() { osreldate, _ = SysctlUint32("kern.osreldate") }) return osreldate >= ver @@ -203,6 +201,8 @@ func setattrlistTimes(path string, times []Timespec, flags int) error { //sys ioctl(fd int, req uint, arg uintptr) (err error) +//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL + func Uname(uname *Utsname) error { mib := []_C_int{CTL_KERN, KERN_OSTYPE} n := unsafe.Sizeof(uname.Sysname) diff --git a/vendor/golang.org/x/sys/unix/syscall_linux.go b/vendor/golang.org/x/sys/unix/syscall_linux.go index ebf3195b65..26903bca8c 100644 --- a/vendor/golang.org/x/sys/unix/syscall_linux.go +++ b/vendor/golang.org/x/sys/unix/syscall_linux.go @@ -884,7 +884,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { for n < len(pp.Path) && pp.Path[n] != 0 { n++ } - bytes := (*[10000]byte)(unsafe.Pointer(&pp.Path[0]))[0:n] + bytes := (*[len(pp.Path)]byte)(unsafe.Pointer(&pp.Path[0]))[0:n] sa.Name = string(bytes) return sa, nil diff --git a/vendor/golang.org/x/sys/unix/syscall_netbsd.go b/vendor/golang.org/x/sys/unix/syscall_netbsd.go index f95463ee28..211131d9cf 100644 --- a/vendor/golang.org/x/sys/unix/syscall_netbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_netbsd.go @@ -18,8 +18,6 @@ import ( "unsafe" ) -//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL - // SockaddrDatalink implements the Sockaddr interface for AF_LINK type sockets. type SockaddrDatalink struct { Len uint8 @@ -189,6 +187,8 @@ func setattrlistTimes(path string, times []Timespec, flags int) error { //sys ioctl(fd int, req uint, arg uintptr) (err error) +//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL + func IoctlGetPtmget(fd int, req uint) (*Ptmget, error) { var value Ptmget err := ioctl(fd, req, uintptr(unsafe.Pointer(&value))) diff --git a/vendor/golang.org/x/sys/unix/syscall_openbsd.go b/vendor/golang.org/x/sys/unix/syscall_openbsd.go index 7fe65ef75f..92ed67de0b 100644 --- a/vendor/golang.org/x/sys/unix/syscall_openbsd.go +++ b/vendor/golang.org/x/sys/unix/syscall_openbsd.go @@ -18,8 +18,6 @@ import ( "unsafe" ) -//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL - // SockaddrDatalink implements the Sockaddr interface for AF_LINK type sockets. type SockaddrDatalink struct { Len uint8 @@ -180,6 +178,8 @@ func setattrlistTimes(path string, times []Timespec, flags int) error { //sys ioctl(fd int, req uint, arg uintptr) (err error) +//sys sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) = SYS___SYSCTL + //sys ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) func Ppoll(fds []PollFd, timeout *Timespec, sigmask *Sigset_t) (n int, err error) { diff --git a/vendor/golang.org/x/sys/unix/syscall_solaris.go b/vendor/golang.org/x/sys/unix/syscall_solaris.go index 62f968c7f0..0e2a696ad3 100644 --- a/vendor/golang.org/x/sys/unix/syscall_solaris.go +++ b/vendor/golang.org/x/sys/unix/syscall_solaris.go @@ -391,7 +391,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) { for n < len(pp.Path) && pp.Path[n] != 0 { n++ } - bytes := (*[10000]byte)(unsafe.Pointer(&pp.Path[0]))[0:n] + bytes := (*[len(pp.Path)]byte)(unsafe.Pointer(&pp.Path[0]))[0:n] sa.Name = string(bytes) return sa, nil diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.1_11.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.1_11.go index b42c1cbadd..b5ed805899 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.1_11.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.1_11.go @@ -547,6 +547,22 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := Syscall6(SYS_SYSCTL, uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(offset>>32), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags), 0, 0) if e1 != 0 { @@ -1683,22 +1699,6 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { - var _p0 unsafe.Pointer - if len(mib) > 0 { - _p0 = unsafe.Pointer(&mib[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - _, _, e1 := Syscall6(SYS___SYSCTL, uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) { _, _, e1 := Syscall6(SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go index 603c9f6eb1..cdf8a70002 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.go @@ -757,6 +757,27 @@ func libc_ioctl_trampoline() // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := syscall_syscall6(funcPC(libc_sysctl_trampoline), uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +func libc_sysctl_trampoline() + +//go:linkname libc_sysctl libc_sysctl +//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall9(funcPC(libc_sendfile_trampoline), uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(offset>>32), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags), 0, 0) if e1 != 0 { @@ -2321,27 +2342,6 @@ func writelen(fd int, buf *byte, nbuf int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { - var _p0 unsafe.Pointer - if len(mib) > 0 { - _p0 = unsafe.Pointer(&mib[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - _, _, e1 := syscall_syscall6(funcPC(libc___sysctl_trampoline), uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -func libc___sysctl_trampoline() - -//go:linkname libc___sysctl libc___sysctl -//go:cgo_import_dynamic libc___sysctl __sysctl "/usr/lib/libSystem.B.dylib" - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) { _, _, e1 := syscall_syscall6(funcPC(libc_ptrace_trampoline), uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s index ece6f67c26..2b1eed2b8b 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_386.s @@ -40,6 +40,8 @@ TEXT ·libc_sendmsg_trampoline(SB),NOSPLIT,$0-0 JMP libc_sendmsg(SB) TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0 JMP libc_kevent(SB) +TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0 + JMP libc___sysctl(SB) TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0 JMP libc_utimes(SB) TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0 @@ -104,8 +106,6 @@ TEXT ·libc_chown_trampoline(SB),NOSPLIT,$0-0 JMP libc_chown(SB) TEXT ·libc_chroot_trampoline(SB),NOSPLIT,$0-0 JMP libc_chroot(SB) -TEXT ·libc_clock_gettime_trampoline(SB),NOSPLIT,$0-0 - JMP libc_clock_gettime(SB) TEXT ·libc_close_trampoline(SB),NOSPLIT,$0-0 JMP libc_close(SB) TEXT ·libc_dup_trampoline(SB),NOSPLIT,$0-0 @@ -262,8 +262,6 @@ TEXT ·libc_mmap_trampoline(SB),NOSPLIT,$0-0 JMP libc_mmap(SB) TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0 JMP libc_munmap(SB) -TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0 - JMP libc___sysctl(SB) TEXT ·libc_ptrace_trampoline(SB),NOSPLIT,$0-0 JMP libc_ptrace(SB) TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.1_11.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.1_11.go index 38b7cbab38..8bde8235a0 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.1_11.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.1_11.go @@ -547,6 +547,22 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := Syscall6(SYS_SYSCTL, uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := Syscall6(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { @@ -1683,22 +1699,6 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { - var _p0 unsafe.Pointer - if len(mib) > 0 { - _p0 = unsafe.Pointer(&mib[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - _, _, e1 := Syscall6(SYS___SYSCTL, uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) { _, _, e1 := Syscall6(SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go index fda478e8bd..63b51fbf00 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go @@ -757,6 +757,27 @@ func libc_ioctl_trampoline() // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := syscall_syscall6(funcPC(libc_sysctl_trampoline), uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +func libc_sysctl_trampoline() + +//go:linkname libc_sysctl libc_sysctl +//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall6(funcPC(libc_sendfile_trampoline), uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { @@ -2321,27 +2342,6 @@ func writelen(fd int, buf *byte, nbuf int) (n int, err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT -func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { - var _p0 unsafe.Pointer - if len(mib) > 0 { - _p0 = unsafe.Pointer(&mib[0]) - } else { - _p0 = unsafe.Pointer(&_zero) - } - _, _, e1 := syscall_syscall6(funcPC(libc___sysctl_trampoline), uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) - if e1 != 0 { - err = errnoErr(e1) - } - return -} - -func libc___sysctl_trampoline() - -//go:linkname libc___sysctl libc___sysctl -//go:cgo_import_dynamic libc___sysctl __sysctl "/usr/lib/libSystem.B.dylib" - -// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT - func ptrace(request int, pid int, addr uintptr, data uintptr) (err error) { _, _, e1 := syscall_syscall6(funcPC(libc_ptrace_trampoline), uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s index 7c4d590163..cdf97d6eea 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s @@ -40,6 +40,8 @@ TEXT ·libc_sendmsg_trampoline(SB),NOSPLIT,$0-0 JMP libc_sendmsg(SB) TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0 JMP libc_kevent(SB) +TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0 + JMP libc___sysctl(SB) TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0 JMP libc_utimes(SB) TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0 @@ -262,8 +264,6 @@ TEXT ·libc_mmap_trampoline(SB),NOSPLIT,$0-0 JMP libc_mmap(SB) TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0 JMP libc_munmap(SB) -TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0 - JMP libc___sysctl(SB) TEXT ·libc_ptrace_trampoline(SB),NOSPLIT,$0-0 JMP libc_ptrace(SB) TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0 diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.1_11.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.1_11.go index abb69183a3..63a236b504 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.1_11.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.1_11.go @@ -547,6 +547,22 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := Syscall6(SYS_SYSCTL, uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := Syscall9(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(offset>>32), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.go index 163b3912d3..adb8668c2b 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm.go @@ -757,6 +757,27 @@ func libc_ioctl_trampoline() // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := syscall_syscall6(funcPC(libc_sysctl_trampoline), uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +func libc_sysctl_trampoline() + +//go:linkname libc_sysctl libc_sysctl +//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall9(funcPC(libc_sendfile_trampoline), uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(offset>>32), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags), 0, 0) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.1_11.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.1_11.go index b75c11d41e..87c0b61221 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.1_11.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.1_11.go @@ -547,6 +547,22 @@ func ioctl(fd int, req uint, arg uintptr) (err error) { // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := Syscall6(SYS_SYSCTL, uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := Syscall6(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go index 7c5bd510e5..c882a4f9d2 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go @@ -757,6 +757,27 @@ func libc_ioctl_trampoline() // THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT +func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) (err error) { + var _p0 unsafe.Pointer + if len(mib) > 0 { + _p0 = unsafe.Pointer(&mib[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + _, _, e1 := syscall_syscall6(funcPC(libc_sysctl_trampoline), uintptr(_p0), uintptr(len(mib)), uintptr(unsafe.Pointer(old)), uintptr(unsafe.Pointer(oldlen)), uintptr(unsafe.Pointer(new)), uintptr(newlen)) + if e1 != 0 { + err = errnoErr(e1) + } + return +} + +func libc_sysctl_trampoline() + +//go:linkname libc_sysctl libc_sysctl +//go:cgo_import_dynamic libc_sysctl sysctl "/usr/lib/libSystem.B.dylib" + +// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT + func sendfile(infd int, outfd int, offset int64, len *int64, hdtr unsafe.Pointer, flags int) (err error) { _, _, e1 := syscall_syscall6(funcPC(libc_sendfile_trampoline), uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags)) if e1 != 0 { diff --git a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s index 96ab9877eb..f57f48f826 100644 --- a/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s +++ b/vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s @@ -40,6 +40,8 @@ TEXT ·libc_sendmsg_trampoline(SB),NOSPLIT,$0-0 JMP libc_sendmsg(SB) TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0 JMP libc_kevent(SB) +TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0 + JMP libc___sysctl(SB) TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0 JMP libc_utimes(SB) TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0 @@ -104,8 +106,6 @@ TEXT ·libc_chown_trampoline(SB),NOSPLIT,$0-0 JMP libc_chown(SB) TEXT ·libc_chroot_trampoline(SB),NOSPLIT,$0-0 JMP libc_chroot(SB) -TEXT ·libc_clock_gettime_trampoline(SB),NOSPLIT,$0-0 - JMP libc_clock_gettime(SB) TEXT ·libc_close_trampoline(SB),NOSPLIT,$0-0 JMP libc_close(SB) TEXT ·libc_dup_trampoline(SB),NOSPLIT,$0-0 diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go index 5f00153f89..4be32a2e55 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go @@ -26,7 +26,10 @@ import ( const iexportVersion = 0 // IExportData returns the binary export data for pkg. +// // If no file set is provided, position info will be missing. +// The package path of the top-level package will not be recorded, +// so that calls to IImportData can override with a provided package path. func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { defer func() { if e := recover(); e != nil { @@ -46,6 +49,7 @@ func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) stringIndex: map[string]uint64{}, declIndex: map[types.Object]uint64{}, typIndex: map[types.Type]uint64{}, + localpkg: pkg, } for i, pt := range predeclared() { @@ -71,7 +75,7 @@ func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) // Append indices to data0 section. dataLen := uint64(p.data0.Len()) w := p.newWriter() - w.writeIndex(p.declIndex, pkg) + w.writeIndex(p.declIndex) w.flush() // Assemble header. @@ -93,14 +97,14 @@ func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) // we're writing out the main index, which is also read by // non-compiler tools and includes a complete package description // (i.e., name and height). -func (w *exportWriter) writeIndex(index map[types.Object]uint64, localpkg *types.Package) { +func (w *exportWriter) writeIndex(index map[types.Object]uint64) { // Build a map from packages to objects from that package. pkgObjs := map[*types.Package][]types.Object{} // For the main index, make sure to include every package that // we reference, even if we're not exporting (or reexporting) // any symbols from it. - pkgObjs[localpkg] = nil + pkgObjs[w.p.localpkg] = nil for pkg := range w.p.allPkgs { pkgObjs[pkg] = nil } @@ -119,12 +123,12 @@ func (w *exportWriter) writeIndex(index map[types.Object]uint64, localpkg *types } sort.Slice(pkgs, func(i, j int) bool { - return pkgs[i].Path() < pkgs[j].Path() + return w.exportPath(pkgs[i]) < w.exportPath(pkgs[j]) }) w.uint64(uint64(len(pkgs))) for _, pkg := range pkgs { - w.string(pkg.Path()) + w.string(w.exportPath(pkg)) w.string(pkg.Name()) w.uint64(uint64(0)) // package height is not needed for go/types @@ -141,6 +145,8 @@ type iexporter struct { fset *token.FileSet out *bytes.Buffer + localpkg *types.Package + // allPkgs tracks all packages that have been referenced by // the export data, so we can ensure to include them in the // main index. @@ -193,6 +199,13 @@ type exportWriter struct { prevLine int64 } +func (w *exportWriter) exportPath(pkg *types.Package) string { + if pkg == w.p.localpkg { + return "" + } + return pkg.Path() +} + func (p *iexporter) doDecl(obj types.Object) { w := p.newWriter() w.setPkg(obj.Pkg(), false) @@ -302,7 +315,7 @@ func (w *exportWriter) pkg(pkg *types.Package) { // Ensure any referenced packages are declared in the main index. w.p.allPkgs[pkg] = true - w.string(pkg.Path()) + w.string(w.exportPath(pkg)) } func (w *exportWriter) qualifiedIdent(obj types.Object) { diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go index 3af95f4a16..a31a880263 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go @@ -147,24 +147,14 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] errorf("no packages found for %s", path) panic("unreachable") } - var localpkg *types.Package - for _, pkg := range pkgList { - if pkg.Path() == path { - localpkg = pkg - break - } - } - if localpkg == nil { - localpkg = pkgList[0] - } - - names := make([]string, 0, len(p.pkgIndex[localpkg])) - for name := range p.pkgIndex[localpkg] { + p.ipkg = pkgList[0] + names := make([]string, 0, len(p.pkgIndex[p.ipkg])) + for name := range p.pkgIndex[p.ipkg] { names = append(names, name) } sort.Strings(names) for _, name := range names { - p.doDecl(localpkg, name) + p.doDecl(p.ipkg, name) } for _, typ := range p.interfaceList { @@ -174,17 +164,18 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] // record all referenced packages as imports list := append(([]*types.Package)(nil), pkgList[1:]...) sort.Sort(byPath(list)) - localpkg.SetImports(list) + p.ipkg.SetImports(list) // package was imported completely and without errors - localpkg.MarkComplete() + p.ipkg.MarkComplete() consumed, _ := r.Seek(0, io.SeekCurrent) - return int(consumed), localpkg, nil + return int(consumed), p.ipkg, nil } type iimporter struct { ipath string + ipkg *types.Package version int stringData []byte @@ -236,6 +227,9 @@ func (p *iimporter) pkgAt(off uint64) *types.Package { return pkg } path := p.stringAt(off) + if path == p.ipath { + return p.ipkg + } errorf("missing package %q in %q", path, p.ipath) return nil } diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 6b341b7e85..7eb88c0751 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -26,6 +26,7 @@ import ( "golang.org/x/tools/go/internal/packagesdriver" "golang.org/x/tools/internal/gopathwalk" "golang.org/x/tools/internal/semver" + "golang.org/x/tools/internal/span" ) // debug controls verbose logging. @@ -281,30 +282,42 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) } dirResponse, err := driver(cfg, pattern) - if err != nil || (len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1) { - // There was an error loading the package. Try to load the file as an ad-hoc package. - // Usually the error will appear in a returned package, but may not if we're in modules mode - // and the ad-hoc is located outside a module. + if err != nil { var queryErr error - dirResponse, queryErr = driver(cfg, query) - if queryErr != nil { - // Return the original error if the attempt to fall back failed. - return err + if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil { + return err // return the original error } - // Special case to handle issue #33482: - // If this is a file= query for ad-hoc packages where the file only exists on an overlay, - // and exists outside of a module, add the file in for the package. - if len(dirResponse.Packages) == 1 && len(dirResponse.Packages) == 1 && - dirResponse.Packages[0].ID == "command-line-arguments" && len(dirResponse.Packages[0].GoFiles) == 0 { - filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath - // TODO(matloob): check if the file is outside of a root dir? - for path := range cfg.Overlay { - if path == filename { - dirResponse.Packages[0].Errors = nil - dirResponse.Packages[0].GoFiles = []string{path} - dirResponse.Packages[0].CompiledGoFiles = []string{path} - } + } + // `go list` can report errors for files that are not listed as part of a package's GoFiles. + // In the case of an invalid Go file, we should assume that it is part of package if only + // one package is in the response. The file may have valid contents in an overlay. + if len(dirResponse.Packages) == 1 { + pkg := dirResponse.Packages[0] + for i, err := range pkg.Errors { + s := errorSpan(err) + if !s.IsValid() { + break } + if len(pkg.CompiledGoFiles) == 0 { + break + } + dir := filepath.Dir(pkg.CompiledGoFiles[0]) + filename := filepath.Join(dir, filepath.Base(s.URI().Filename())) + if info, err := os.Stat(filename); err != nil || info.IsDir() { + break + } + if !contains(pkg.CompiledGoFiles, filename) { + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename) + pkg.GoFiles = append(pkg.GoFiles, filename) + pkg.Errors = append(pkg.Errors[:i], pkg.Errors[i+1:]...) + } + } + } + // A final attempt to construct an ad-hoc package. + if len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1 { + var queryErr error + if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil { + return err // return the original error } } isRoot := make(map[string]bool, len(dirResponse.Roots)) @@ -332,6 +345,74 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q return nil } +// adHocPackage attempts to construct an ad-hoc package given a query that failed. +func adHocPackage(cfg *Config, driver driver, pattern, query string) (*driverResponse, error) { + // There was an error loading the package. Try to load the file as an ad-hoc package. + // Usually the error will appear in a returned package, but may not if we're in modules mode + // and the ad-hoc is located outside a module. + dirResponse, err := driver(cfg, query) + if err != nil { + return nil, err + } + // If we get nothing back from `go list`, try to make this file into its own ad-hoc package. + if len(dirResponse.Packages) == 0 && err == nil { + dirResponse.Packages = append(dirResponse.Packages, &Package{ + ID: "command-line-arguments", + PkgPath: query, + GoFiles: []string{query}, + CompiledGoFiles: []string{query}, + Imports: make(map[string]*Package), + }) + dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments") + } + // Special case to handle issue #33482: + // If this is a file= query for ad-hoc packages where the file only exists on an overlay, + // and exists outside of a module, add the file in for the package. + if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" || dirResponse.Packages[0].PkgPath == filepath.ToSlash(query)) { + if len(dirResponse.Packages[0].GoFiles) == 0 { + filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath + // TODO(matloob): check if the file is outside of a root dir? + for path := range cfg.Overlay { + if path == filename { + dirResponse.Packages[0].Errors = nil + dirResponse.Packages[0].GoFiles = []string{path} + dirResponse.Packages[0].CompiledGoFiles = []string{path} + } + } + } + } + return dirResponse, nil +} + +func contains(files []string, filename string) bool { + for _, f := range files { + if f == filename { + return true + } + } + return false +} + +// errorSpan attempts to parse a standard `go list` error message +// by stripping off the trailing error message. +// +// It works only on errors whose message is prefixed by colon, +// followed by a space (": "). For example: +// +// attributes.go:13:1: expected 'package', found 'type' +// +func errorSpan(err Error) span.Span { + if err.Pos == "" { + input := strings.TrimSpace(err.Msg) + msgIndex := strings.Index(input, ": ") + if msgIndex < 0 { + return span.Parse(input) + } + return span.Parse(input[:msgIndex]) + } + return span.Parse(err.Pos) +} + // modCacheRegexp splits a path in a module cache into module, module version, and package. var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) @@ -395,6 +476,10 @@ func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, quer } files, err := ioutil.ReadDir(modRoot) + if err != nil { + panic(err) // See above. + } + for _, f := range files { if strings.HasSuffix(f.Name(), ".go") { simpleMatches = append(simpleMatches, rel) @@ -462,7 +547,7 @@ func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, quer // We're only trying to look at stuff in the module cache, so // disable the network. This should speed things up, and has // prevented errors in at least one case, #28518. - tmpCfg.Env = append(append([]string{"GOPROXY=off"}, cfg.Env...)) + tmpCfg.Env = append([]string{"GOPROXY=off"}, cfg.Env...) var err error tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery") @@ -510,17 +595,29 @@ func roots(cfg *Config) ([]gopathwalk.Root, string, error) { var roots []gopathwalk.Root // Always add GOROOT. - roots = append(roots, gopathwalk.Root{filepath.Join(goroot, "/src"), gopathwalk.RootGOROOT}) + roots = append(roots, gopathwalk.Root{ + Path: filepath.Join(goroot, "/src"), + Type: gopathwalk.RootGOROOT, + }) // If modules are enabled, scan the module dir. if modDir != "" { - roots = append(roots, gopathwalk.Root{modDir, gopathwalk.RootCurrentModule}) + roots = append(roots, gopathwalk.Root{ + Path: modDir, + Type: gopathwalk.RootCurrentModule, + }) } // Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode. for _, p := range gopath { if modDir != "" { - roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache}) + roots = append(roots, gopathwalk.Root{ + Path: filepath.Join(p, "/pkg/mod"), + Type: gopathwalk.RootModuleCache, + }) } else { - roots = append(roots, gopathwalk.Root{filepath.Join(p, "/src"), gopathwalk.RootGOPATH}) + roots = append(roots, gopathwalk.Root{ + Path: filepath.Join(p, "/src"), + Type: gopathwalk.RootGOPATH, + }) } } @@ -682,7 +779,7 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv // contained in a known module or GOPATH entry. This will allow the package to be // properly "reclaimed" when overlays are processed. if filepath.IsAbs(p.ImportPath) && p.Error != nil { - pkgPath, ok := getPkgPath(p.ImportPath, rootsDirs) + pkgPath, ok := getPkgPath(cfg, p.ImportPath, rootsDirs) if ok { p.ImportPath = pkgPath } @@ -792,15 +889,31 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv } // getPkgPath finds the package path of a directory if it's relative to a root directory. -func getPkgPath(dir string, goInfo func() *goInfo) (string, bool) { +func getPkgPath(cfg *Config, dir string, goInfo func() *goInfo) (string, bool) { + absDir, err := filepath.Abs(dir) + if err != nil { + cfg.Logf("error getting absolute path of %s: %v", dir, err) + return "", false + } for rdir, rpath := range goInfo().rootDirs { + absRdir, err := filepath.Abs(rdir) + if err != nil { + cfg.Logf("error getting absolute path of %s: %v", rdir, err) + continue + } + // Make sure that the directory is in the module, + // to avoid creating a path relative to another module. + if !strings.HasPrefix(absDir, absRdir) { + cfg.Logf("%s does not have prefix %s", absDir, absRdir) + continue + } // TODO(matloob): This doesn't properly handle symlinks. r, err := filepath.Rel(rdir, dir) if err != nil { continue } if rpath != "" { - // We choose only ore root even though the directory even it can belong in multiple modules + // We choose only one root even though the directory even it can belong in multiple modules // or GOPATH entries. This is okay because we only need to work with absolute dirs when a // file is missing from disk, for instance when gopls calls go/packages in an overlay. // Once the file is saved, gopls, or the next invocation of the tool will get the correct @@ -808,6 +921,7 @@ func getPkgPath(dir string, goInfo func() *goInfo) (string, bool) { // TODO(matloob): Implement module tiebreaking? return path.Join(rpath, filepath.ToSlash(r)), true } + return filepath.ToSlash(r), true } return "", false } @@ -859,7 +973,7 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { cmd.Stdout = stdout cmd.Stderr = stderr defer func(start time.Time) { - cfg.Logf("%s for %v, stderr: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr) + cfg.Logf("%s for %v, stderr: <<%s>> stdout: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr, stdout) }(time.Now()) if err := cmd.Run(); err != nil { @@ -896,7 +1010,7 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { // (the Graphic characters without spaces) and may also exclude the // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && - strings.IndexRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) == -1 + !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) } if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { if strings.HasPrefix(strings.TrimLeftFunc(stderr.String()[len("# "):], isPkgPathRune), "\n") { @@ -954,6 +1068,10 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { // status if there's a dependency on a package that doesn't exist. But it should return // a zero exit status and set an error on that package. if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { + // Don't clobber stdout if `go list` actually returned something. + if len(stdout.String()) > 0 { + return stdout, nil + } // try to extract package name from string stderrStr := stderr.String() var importPath string @@ -987,12 +1105,6 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr) } - - // debugging - if false { - fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cmd, args...), stdout) - } - return stdout, nil } diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go index 308e79c55c..a7de62299d 100644 --- a/vendor/golang.org/x/tools/go/packages/golist_overlay.go +++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go @@ -6,7 +6,6 @@ import ( "fmt" "go/parser" "go/token" - "path" "path/filepath" "strconv" "strings" @@ -87,26 +86,10 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func( if pkg == nil { // Try to find the module or gopath dir the file is contained in. // Then for modules, add the module opath to the beginning. - var pkgPath string - for rdir, rpath := range rootDirs().rootDirs { - // TODO(matloob): This doesn't properly handle symlinks. - r, err := filepath.Rel(rdir, dir) - if err != nil { - continue - } - pkgPath = filepath.ToSlash(r) - if rpath != "" { - pkgPath = path.Join(rpath, pkgPath) - } - // We only create one new package even it can belong in multiple modules or GOPATH entries. - // This is okay because tools (such as the LSP) that use overlays will recompute the overlay - // once the file is saved, and golist will do the right thing. - // TODO(matloob): Implement module tiebreaking? + pkgPath, ok := getPkgPath(cfg, dir, rootDirs) + if !ok { break } - if pkgPath == "" { - continue - } isXTest := strings.HasSuffix(pkgName, "_test") if isXTest { pkgPath += "_test" diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go index 60eb67b697..9a61bdbf5d 100644 --- a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go +++ b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go @@ -16,6 +16,7 @@ import ( "os" "path/filepath" "strings" + "time" "golang.org/x/tools/internal/fastwalk" ) @@ -83,8 +84,9 @@ func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) } return } + start := time.Now() if opts.Debug { - log.Printf("scanning %s", root.Path) + log.Printf("gopathwalk: scanning %s", root.Path) } w := &walker{ root: root, @@ -98,7 +100,7 @@ func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) } if opts.Debug { - log.Printf("scanned %s", root.Path) + log.Printf("gopathwalk: scanned %s in %v", root.Path, time.Since(start)) } } diff --git a/vendor/golang.org/x/tools/internal/span/parse.go b/vendor/golang.org/x/tools/internal/span/parse.go new file mode 100644 index 0000000000..b3f268a38a --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/parse.go @@ -0,0 +1,100 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "strconv" + "strings" + "unicode/utf8" +) + +// Parse returns the location represented by the input. +// All inputs are valid locations, as they can always be a pure filename. +// The returned span will be normalized, and thus if printed may produce a +// different string. +func Parse(input string) Span { + // :0:0#0-0:0#0 + valid := input + var hold, offset int + hadCol := false + suf := rstripSuffix(input) + if suf.sep == "#" { + offset = suf.num + suf = rstripSuffix(suf.remains) + } + if suf.sep == ":" { + valid = suf.remains + hold = suf.num + hadCol = true + suf = rstripSuffix(suf.remains) + } + switch { + case suf.sep == ":": + return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), Point{}) + case suf.sep == "-": + // we have a span, fall out of the case to continue + default: + // separator not valid, rewind to either the : or the start + return New(NewURI(valid), NewPoint(hold, 0, offset), Point{}) + } + // only the span form can get here + // at this point we still don't know what the numbers we have mean + // if have not yet seen a : then we might have either a line or a column depending + // on whether start has a column or not + // we build an end point and will fix it later if needed + end := NewPoint(suf.num, hold, offset) + hold, offset = 0, 0 + suf = rstripSuffix(suf.remains) + if suf.sep == "#" { + offset = suf.num + suf = rstripSuffix(suf.remains) + } + if suf.sep != ":" { + // turns out we don't have a span after all, rewind + return New(NewURI(valid), end, Point{}) + } + valid = suf.remains + hold = suf.num + suf = rstripSuffix(suf.remains) + if suf.sep != ":" { + // line#offset only + return New(NewURI(valid), NewPoint(hold, 0, offset), end) + } + // we have a column, so if end only had one number, it is also the column + if !hadCol { + end = NewPoint(suf.num, end.v.Line, end.v.Offset) + } + return New(NewURI(suf.remains), NewPoint(suf.num, hold, offset), end) +} + +type suffix struct { + remains string + sep string + num int +} + +func rstripSuffix(input string) suffix { + if len(input) == 0 { + return suffix{"", "", -1} + } + remains := input + num := -1 + // first see if we have a number at the end + last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' }) + if last >= 0 && last < len(remains)-1 { + number, err := strconv.ParseInt(remains[last+1:], 10, 64) + if err == nil { + num = int(number) + remains = remains[:last+1] + } + } + // now see if we have a trailing separator + r, w := utf8.DecodeLastRuneInString(remains) + if r != ':' && r != '#' && r == '#' { + return suffix{input, "", -1} + } + remains = remains[:len(remains)-w] + return suffix{remains, string(r), num} +} diff --git a/vendor/golang.org/x/tools/internal/span/span.go b/vendor/golang.org/x/tools/internal/span/span.go new file mode 100644 index 0000000000..4d2ad09866 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/span.go @@ -0,0 +1,285 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package span contains support for representing with positions and ranges in +// text files. +package span + +import ( + "encoding/json" + "fmt" + "path" +) + +// Span represents a source code range in standardized form. +type Span struct { + v span +} + +// Point represents a single point within a file. +// In general this should only be used as part of a Span, as on its own it +// does not carry enough information. +type Point struct { + v point +} + +type span struct { + URI URI `json:"uri"` + Start point `json:"start"` + End point `json:"end"` +} + +type point struct { + Line int `json:"line"` + Column int `json:"column"` + Offset int `json:"offset"` +} + +// Invalid is a span that reports false from IsValid +var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}} + +var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}} + +// Converter is the interface to an object that can convert between line:column +// and offset forms for a single file. +type Converter interface { + //ToPosition converts from an offset to a line:column pair. + ToPosition(offset int) (int, int, error) + //ToOffset converts from a line:column pair to an offset. + ToOffset(line, col int) (int, error) +} + +func New(uri URI, start Point, end Point) Span { + s := Span{v: span{URI: uri, Start: start.v, End: end.v}} + s.v.clean() + return s +} + +func NewPoint(line, col, offset int) Point { + p := Point{v: point{Line: line, Column: col, Offset: offset}} + p.v.clean() + return p +} + +func Compare(a, b Span) int { + if r := CompareURI(a.URI(), b.URI()); r != 0 { + return r + } + if r := comparePoint(a.v.Start, b.v.Start); r != 0 { + return r + } + return comparePoint(a.v.End, b.v.End) +} + +func ComparePoint(a, b Point) int { + return comparePoint(a.v, b.v) +} + +func comparePoint(a, b point) int { + if !a.hasPosition() { + if a.Offset < b.Offset { + return -1 + } + if a.Offset > b.Offset { + return 1 + } + return 0 + } + if a.Line < b.Line { + return -1 + } + if a.Line > b.Line { + return 1 + } + if a.Column < b.Column { + return -1 + } + if a.Column > b.Column { + return 1 + } + return 0 +} + +func (s Span) HasPosition() bool { return s.v.Start.hasPosition() } +func (s Span) HasOffset() bool { return s.v.Start.hasOffset() } +func (s Span) IsValid() bool { return s.v.Start.isValid() } +func (s Span) IsPoint() bool { return s.v.Start == s.v.End } +func (s Span) URI() URI { return s.v.URI } +func (s Span) Start() Point { return Point{s.v.Start} } +func (s Span) End() Point { return Point{s.v.End} } +func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) } +func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) } + +func (p Point) HasPosition() bool { return p.v.hasPosition() } +func (p Point) HasOffset() bool { return p.v.hasOffset() } +func (p Point) IsValid() bool { return p.v.isValid() } +func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) } +func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) } +func (p Point) Line() int { + if !p.v.hasPosition() { + panic(fmt.Errorf("position not set in %v", p.v)) + } + return p.v.Line +} +func (p Point) Column() int { + if !p.v.hasPosition() { + panic(fmt.Errorf("position not set in %v", p.v)) + } + return p.v.Column +} +func (p Point) Offset() int { + if !p.v.hasOffset() { + panic(fmt.Errorf("offset not set in %v", p.v)) + } + return p.v.Offset +} + +func (p point) hasPosition() bool { return p.Line > 0 } +func (p point) hasOffset() bool { return p.Offset >= 0 } +func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() } +func (p point) isZero() bool { + return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0) +} + +func (s *span) clean() { + //this presumes the points are already clean + if !s.End.isValid() || (s.End == point{}) { + s.End = s.Start + } +} + +func (p *point) clean() { + if p.Line < 0 { + p.Line = 0 + } + if p.Column <= 0 { + if p.Line > 0 { + p.Column = 1 + } else { + p.Column = 0 + } + } + if p.Offset == 0 && (p.Line > 1 || p.Column > 1) { + p.Offset = -1 + } +} + +// Format implements fmt.Formatter to print the Location in a standard form. +// The format produced is one that can be read back in using Parse. +func (s Span) Format(f fmt.State, c rune) { + fullForm := f.Flag('+') + preferOffset := f.Flag('#') + // we should always have a uri, simplify if it is file format + //TODO: make sure the end of the uri is unambiguous + uri := string(s.v.URI) + if c == 'f' { + uri = path.Base(uri) + } else if !fullForm { + uri = s.v.URI.Filename() + } + fmt.Fprint(f, uri) + if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) { + return + } + // see which bits of start to write + printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition()) + printLine := s.HasPosition() && (fullForm || !printOffset) + printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1)) + fmt.Fprint(f, ":") + if printLine { + fmt.Fprintf(f, "%d", s.v.Start.Line) + } + if printColumn { + fmt.Fprintf(f, ":%d", s.v.Start.Column) + } + if printOffset { + fmt.Fprintf(f, "#%d", s.v.Start.Offset) + } + // start is written, do we need end? + if s.IsPoint() { + return + } + // we don't print the line if it did not change + printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line) + fmt.Fprint(f, "-") + if printLine { + fmt.Fprintf(f, "%d", s.v.End.Line) + } + if printColumn { + if printLine { + fmt.Fprint(f, ":") + } + fmt.Fprintf(f, "%d", s.v.End.Column) + } + if printOffset { + fmt.Fprintf(f, "#%d", s.v.End.Offset) + } +} + +func (s Span) WithPosition(c Converter) (Span, error) { + if err := s.update(c, true, false); err != nil { + return Span{}, err + } + return s, nil +} + +func (s Span) WithOffset(c Converter) (Span, error) { + if err := s.update(c, false, true); err != nil { + return Span{}, err + } + return s, nil +} + +func (s Span) WithAll(c Converter) (Span, error) { + if err := s.update(c, true, true); err != nil { + return Span{}, err + } + return s, nil +} + +func (s *Span) update(c Converter, withPos, withOffset bool) error { + if !s.IsValid() { + return fmt.Errorf("cannot add information to an invalid span") + } + if withPos && !s.HasPosition() { + if err := s.v.Start.updatePosition(c); err != nil { + return err + } + if s.v.End.Offset == s.v.Start.Offset { + s.v.End = s.v.Start + } else if err := s.v.End.updatePosition(c); err != nil { + return err + } + } + if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) { + if err := s.v.Start.updateOffset(c); err != nil { + return err + } + if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column { + s.v.End.Offset = s.v.Start.Offset + } else if err := s.v.End.updateOffset(c); err != nil { + return err + } + } + return nil +} + +func (p *point) updatePosition(c Converter) error { + line, col, err := c.ToPosition(p.Offset) + if err != nil { + return err + } + p.Line = line + p.Column = col + return nil +} + +func (p *point) updateOffset(c Converter) error { + offset, err := c.ToOffset(p.Line, p.Column) + if err != nil { + return err + } + p.Offset = offset + return nil +} diff --git a/vendor/golang.org/x/tools/internal/span/token.go b/vendor/golang.org/x/tools/internal/span/token.go new file mode 100644 index 0000000000..ce44541b2f --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/token.go @@ -0,0 +1,151 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "fmt" + "go/token" +) + +// Range represents a source code range in token.Pos form. +// It also carries the FileSet that produced the positions, so that it is +// self contained. +type Range struct { + FileSet *token.FileSet + Start token.Pos + End token.Pos +} + +// TokenConverter is a Converter backed by a token file set and file. +// It uses the file set methods to work out the conversions, which +// makes it fast and does not require the file contents. +type TokenConverter struct { + fset *token.FileSet + file *token.File +} + +// NewRange creates a new Range from a FileSet and two positions. +// To represent a point pass a 0 as the end pos. +func NewRange(fset *token.FileSet, start, end token.Pos) Range { + return Range{ + FileSet: fset, + Start: start, + End: end, + } +} + +// NewTokenConverter returns an implementation of Converter backed by a +// token.File. +func NewTokenConverter(fset *token.FileSet, f *token.File) *TokenConverter { + return &TokenConverter{fset: fset, file: f} +} + +// NewContentConverter returns an implementation of Converter for the +// given file content. +func NewContentConverter(filename string, content []byte) *TokenConverter { + fset := token.NewFileSet() + f := fset.AddFile(filename, -1, len(content)) + f.SetLinesForContent(content) + return &TokenConverter{fset: fset, file: f} +} + +// IsPoint returns true if the range represents a single point. +func (r Range) IsPoint() bool { + return r.Start == r.End +} + +// Span converts a Range to a Span that represents the Range. +// It will fill in all the members of the Span, calculating the line and column +// information. +func (r Range) Span() (Span, error) { + f := r.FileSet.File(r.Start) + if f == nil { + return Span{}, fmt.Errorf("file not found in FileSet") + } + s := Span{v: span{URI: FileURI(f.Name())}} + var err error + s.v.Start.Offset, err = offset(f, r.Start) + if err != nil { + return Span{}, err + } + if r.End.IsValid() { + s.v.End.Offset, err = offset(f, r.End) + if err != nil { + return Span{}, err + } + } + s.v.Start.clean() + s.v.End.clean() + s.v.clean() + converter := NewTokenConverter(r.FileSet, f) + return s.WithPosition(converter) +} + +// offset is a copy of the Offset function in go/token, but with the adjustment +// that it does not panic on invalid positions. +func offset(f *token.File, pos token.Pos) (int, error) { + if int(pos) < f.Base() || int(pos) > f.Base()+f.Size() { + return 0, fmt.Errorf("invalid pos") + } + return int(pos) - f.Base(), nil +} + +// Range converts a Span to a Range that represents the Span for the supplied +// File. +func (s Span) Range(converter *TokenConverter) (Range, error) { + s, err := s.WithOffset(converter) + if err != nil { + return Range{}, err + } + // go/token will panic if the offset is larger than the file's size, + // so check here to avoid panicking. + if s.Start().Offset() > converter.file.Size() { + return Range{}, fmt.Errorf("start offset %v is past the end of the file %v", s.Start(), converter.file.Size()) + } + if s.End().Offset() > converter.file.Size() { + return Range{}, fmt.Errorf("end offset %v is past the end of the file %v", s.End(), converter.file.Size()) + } + return Range{ + FileSet: converter.fset, + Start: converter.file.Pos(s.Start().Offset()), + End: converter.file.Pos(s.End().Offset()), + }, nil +} + +func (l *TokenConverter) ToPosition(offset int) (int, int, error) { + if offset > l.file.Size() { + return 0, 0, fmt.Errorf("offset %v is past the end of the file %v", offset, l.file.Size()) + } + pos := l.file.Pos(offset) + p := l.fset.Position(pos) + if offset == l.file.Size() { + return p.Line + 1, 1, nil + } + return p.Line, p.Column, nil +} + +func (l *TokenConverter) ToOffset(line, col int) (int, error) { + if line < 0 { + return -1, fmt.Errorf("line is not valid") + } + lineMax := l.file.LineCount() + 1 + if line > lineMax { + return -1, fmt.Errorf("line is beyond end of file %v", lineMax) + } else if line == lineMax { + if col > 1 { + return -1, fmt.Errorf("column is beyond end of file") + } + // at the end of the file, allowing for a trailing eol + return l.file.Size(), nil + } + pos := lineStart(l.file, line) + if !pos.IsValid() { + return -1, fmt.Errorf("line is not in file") + } + // we assume that column is in bytes here, and that the first byte of a + // line is at column 1 + pos += token.Pos(col - 1) + return offset(l.file, pos) +} diff --git a/vendor/golang.org/x/tools/internal/span/token111.go b/vendor/golang.org/x/tools/internal/span/token111.go new file mode 100644 index 0000000000..bf7a5406b6 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/token111.go @@ -0,0 +1,39 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !go1.12 + +package span + +import ( + "go/token" +) + +// lineStart is the pre-Go 1.12 version of (*token.File).LineStart. For Go +// versions <= 1.11, we borrow logic from the analysisutil package. +// TODO(rstambler): Delete this file when we no longer support Go 1.11. +func lineStart(f *token.File, line int) token.Pos { + // Use binary search to find the start offset of this line. + + min := 0 // inclusive + max := f.Size() // exclusive + for { + offset := (min + max) / 2 + pos := f.Pos(offset) + posn := f.Position(pos) + if posn.Line == line { + return pos - (token.Pos(posn.Column) - 1) + } + + if min+1 >= max { + return token.NoPos + } + + if posn.Line < line { + min = offset + } else { + max = offset + } + } +} diff --git a/vendor/golang.org/x/tools/internal/span/token112.go b/vendor/golang.org/x/tools/internal/span/token112.go new file mode 100644 index 0000000000..017aec9c13 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/token112.go @@ -0,0 +1,16 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build go1.12 + +package span + +import ( + "go/token" +) + +// TODO(rstambler): Delete this file when we no longer support Go 1.11. +func lineStart(f *token.File, line int) token.Pos { + return f.LineStart(line) +} diff --git a/vendor/golang.org/x/tools/internal/span/uri.go b/vendor/golang.org/x/tools/internal/span/uri.go new file mode 100644 index 0000000000..e05a9e6ef5 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/uri.go @@ -0,0 +1,152 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "unicode" +) + +const fileScheme = "file" + +// URI represents the full URI for a file. +type URI string + +// Filename returns the file path for the given URI. +// It is an error to call this on a URI that is not a valid filename. +func (uri URI) Filename() string { + filename, err := filename(uri) + if err != nil { + panic(err) + } + return filepath.FromSlash(filename) +} + +func filename(uri URI) (string, error) { + if uri == "" { + return "", nil + } + u, err := url.ParseRequestURI(string(uri)) + if err != nil { + return "", err + } + if u.Scheme != fileScheme { + return "", fmt.Errorf("only file URIs are supported, got %q from %q", u.Scheme, uri) + } + if isWindowsDriveURI(u.Path) { + u.Path = u.Path[1:] + } + return u.Path, nil +} + +// NewURI returns a span URI for the string. +// It will attempt to detect if the string is a file path or uri. +func NewURI(s string) URI { + if u, err := url.PathUnescape(s); err == nil { + s = u + } + if strings.HasPrefix(s, fileScheme+"://") { + return URI(s) + } + return FileURI(s) +} + +func CompareURI(a, b URI) int { + if equalURI(a, b) { + return 0 + } + if a < b { + return -1 + } + return 1 +} + +func equalURI(a, b URI) bool { + if a == b { + return true + } + // If we have the same URI basename, we may still have the same file URIs. + if !strings.EqualFold(path.Base(string(a)), path.Base(string(b))) { + return false + } + fa, err := filename(a) + if err != nil { + return false + } + fb, err := filename(b) + if err != nil { + return false + } + // Stat the files to check if they are equal. + infoa, err := os.Stat(filepath.FromSlash(fa)) + if err != nil { + return false + } + infob, err := os.Stat(filepath.FromSlash(fb)) + if err != nil { + return false + } + return os.SameFile(infoa, infob) +} + +// FileURI returns a span URI for the supplied file path. +// It will always have the file scheme. +func FileURI(path string) URI { + if path == "" { + return "" + } + // Handle standard library paths that contain the literal "$GOROOT". + // TODO(rstambler): The go/packages API should allow one to determine a user's $GOROOT. + const prefix = "$GOROOT" + if len(path) >= len(prefix) && strings.EqualFold(prefix, path[:len(prefix)]) { + suffix := path[len(prefix):] + path = runtime.GOROOT() + suffix + } + if !isWindowsDrivePath(path) { + if abs, err := filepath.Abs(path); err == nil { + path = abs + } + } + // Check the file path again, in case it became absolute. + if isWindowsDrivePath(path) { + path = "/" + path + } + path = filepath.ToSlash(path) + u := url.URL{ + Scheme: fileScheme, + Path: path, + } + uri := u.String() + if unescaped, err := url.PathUnescape(uri); err == nil { + uri = unescaped + } + return URI(uri) +} + +// isWindowsDrivePath returns true if the file path is of the form used by +// Windows. We check if the path begins with a drive letter, followed by a ":". +func isWindowsDrivePath(path string) bool { + if len(path) < 4 { + return false + } + return unicode.IsLetter(rune(path[0])) && path[1] == ':' +} + +// isWindowsDriveURI returns true if the file URI is of the format used by +// Windows URIs. The url.Parse package does not specially handle Windows paths +// (see https://golang.org/issue/6027). We check if the URI path has +// a drive prefix (e.g. "/C:"). If so, we trim the leading "/". +func isWindowsDriveURI(uri string) bool { + if len(uri) < 4 { + return false + } + return uri[0] == '/' && unicode.IsLetter(rune(uri[1])) && uri[2] == ':' +} diff --git a/vendor/golang.org/x/tools/internal/span/utf16.go b/vendor/golang.org/x/tools/internal/span/utf16.go new file mode 100644 index 0000000000..561b3fa50a --- /dev/null +++ b/vendor/golang.org/x/tools/internal/span/utf16.go @@ -0,0 +1,94 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package span + +import ( + "fmt" + "unicode/utf16" + "unicode/utf8" +) + +// ToUTF16Column calculates the utf16 column expressed by the point given the +// supplied file contents. +// This is used to convert from the native (always in bytes) column +// representation and the utf16 counts used by some editors. +func ToUTF16Column(p Point, content []byte) (int, error) { + if content == nil { + return -1, fmt.Errorf("ToUTF16Column: missing content") + } + if !p.HasPosition() { + return -1, fmt.Errorf("ToUTF16Column: point is missing position") + } + if !p.HasOffset() { + return -1, fmt.Errorf("ToUTF16Column: point is missing offset") + } + offset := p.Offset() // 0-based + colZero := p.Column() - 1 // 0-based + if colZero == 0 { + // 0-based column 0, so it must be chr 1 + return 1, nil + } else if colZero < 0 { + return -1, fmt.Errorf("ToUTF16Column: column is invalid (%v)", colZero) + } + // work out the offset at the start of the line using the column + lineOffset := offset - colZero + if lineOffset < 0 || offset > len(content) { + return -1, fmt.Errorf("ToUTF16Column: offsets %v-%v outside file contents (%v)", lineOffset, offset, len(content)) + } + // Use the offset to pick out the line start. + // This cannot panic: offset > len(content) and lineOffset < offset. + start := content[lineOffset:] + + // Now, truncate down to the supplied column. + start = start[:colZero] + + // and count the number of utf16 characters + // in theory we could do this by hand more efficiently... + return len(utf16.Encode([]rune(string(start)))) + 1, nil +} + +// FromUTF16Column advances the point by the utf16 character offset given the +// supplied line contents. +// This is used to convert from the utf16 counts used by some editors to the +// native (always in bytes) column representation. +func FromUTF16Column(p Point, chr int, content []byte) (Point, error) { + if !p.HasOffset() { + return Point{}, fmt.Errorf("FromUTF16Column: point is missing offset") + } + // if chr is 1 then no adjustment needed + if chr <= 1 { + return p, nil + } + if p.Offset() >= len(content) { + return p, fmt.Errorf("FromUTF16Column: offset (%v) greater than length of content (%v)", p.Offset(), len(content)) + } + remains := content[p.Offset():] + // scan forward the specified number of characters + for count := 1; count < chr; count++ { + if len(remains) <= 0 { + return Point{}, fmt.Errorf("FromUTF16Column: chr goes beyond the content") + } + r, w := utf8.DecodeRune(remains) + if r == '\n' { + // Per the LSP spec: + // + // > If the character value is greater than the line length it + // > defaults back to the line length. + break + } + remains = remains[w:] + if r >= 0x10000 { + // a two point rune + count++ + // if we finished in a two point rune, do not advance past the first + if count >= chr { + break + } + } + p.v.Column += w + p.v.Offset += w + } + return p, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6ec02530ac..3030a4bb5a 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332 +# code.vikunja.io/web v0.0.0-20191023202526-f337750c3573 code.vikunja.io/web code.vikunja.io/web/handler # github.com/BurntSushi/toml v0.3.1 @@ -86,10 +86,10 @@ github.com/inconshreveable/mousetrap # github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb github.com/jgautheron/goconst/cmd/goconst github.com/jgautheron/goconst -# github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f +# github.com/labstack/echo/v4 v4.1.11 github.com/labstack/echo/v4 github.com/labstack/echo/v4/middleware -# github.com/labstack/gommon v0.2.9 +# github.com/labstack/gommon v0.3.0 github.com/labstack/gommon/log github.com/labstack/gommon/color github.com/labstack/gommon/bytes @@ -102,7 +102,7 @@ github.com/magiconair/properties github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jwriter github.com/mailru/easyjson/buffer -# github.com/mattn/go-colorable v0.1.2 +# github.com/mattn/go-colorable v0.1.4 github.com/mattn/go-colorable # github.com/mattn/go-isatty v0.0.10 github.com/mattn/go-isatty @@ -187,7 +187,7 @@ github.com/ulule/limiter/v3/drivers/store/common github.com/urfave/cli # github.com/valyala/bytebufferpool v1.0.0 github.com/valyala/bytebufferpool -# github.com/valyala/fasttemplate v1.0.1 +# github.com/valyala/fasttemplate v1.1.0 github.com/valyala/fasttemplate # golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 golang.org/x/crypto/bcrypt @@ -197,9 +197,9 @@ golang.org/x/crypto/blowfish # golang.org/x/lint v0.0.0-20190409202823-959b441ac422 golang.org/x/lint/golint golang.org/x/lint -# golang.org/x/net v0.0.0-20191011234655-491137f69257 +# golang.org/x/net v0.0.0-20191021144547-ec77196f6094 golang.org/x/net/idna -# golang.org/x/sys v0.0.0-20191010194322-b09406accb47 +# golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 golang.org/x/sys/unix # golang.org/x/text v0.3.2 golang.org/x/text/transform @@ -207,7 +207,7 @@ golang.org/x/text/unicode/norm golang.org/x/text/secure/bidirule golang.org/x/text/unicode/bidi golang.org/x/text/width -# golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a +# golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 golang.org/x/tools/go/loader golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/gcexportdata @@ -220,6 +220,7 @@ golang.org/x/tools/go/internal/gcimporter golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/semver +golang.org/x/tools/internal/span golang.org/x/tools/internal/fastwalk # google.golang.org/appengine v1.5.0 google.golang.org/appengine/cloudsql