Improve pagination (#105)

This commit is contained in:
konrad 2019-10-23 21:11:40 +00:00
parent 89f385a53d
commit 8948a5f219
97 changed files with 2137 additions and 529 deletions

View file

@ -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 # 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. # with a config file which will then be used.
rootpath: <rootpath> rootpath: <rootpath>
# The number of items which gets returned per page # The max number of items which can be returned per page
pagecount: 50 maxitemsperpage: 50
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system # 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 # You'll need to use redis for this in order to enable common metrics over multiple nodes
enablemetrics: false enablemetrics: false

View file

@ -27,7 +27,7 @@ You can feed this function directly into xorm's `Limit`-Function like so:
{{< highlight golang >}} {{< highlight golang >}}
lists := []List{} lists := []List{}
err := x.Limit(getLimitFromPageIndex(pageIndex)).Find(&lists) err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&lists)
{{< /highlight >}} {{< /highlight >}}
// TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint? // TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint?

View file

@ -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 # 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. # with a config file which will then be used.
rootpath: <the path of the executable> rootpath: <the path of the executable>
# The number of items which gets returned per page # The max number of items which can be returned per page
pagecount: 50 maxitemsperpage: 50
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system # 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 # You'll need to use redis for this in order to enable common metrics over multiple nodes
enablemetrics: false enablemetrics: false

14
go.mod
View file

@ -18,7 +18,7 @@ module code.vikunja.io/api
require ( require (
cloud.google.com/go v0.34.0 // indirect 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/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/beevik/etree v1.1.0 // indirect github.com/beevik/etree v1.1.0 // indirect
@ -46,10 +46,11 @@ require (
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb
github.com/json-iterator/go v1.1.7 // indirect github.com/json-iterator/go v1.1.7 // indirect
github.com/kr/pty v1.1.8 // indirect github.com/kr/pty v1.1.8 // indirect
github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f github.com/labstack/echo/v4 v4.1.11
github.com/labstack/gommon v0.2.9 github.com/labstack/gommon v0.3.0
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
github.com/mailru/easyjson v0.7.0 // indirect 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-isatty v0.0.10 // indirect
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect
github.com/mattn/go-runewidth v0.0.4 // 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/ugorji/go v1.1.7 // indirect
github.com/ulule/limiter/v3 v3.3.0 github.com/ulule/limiter/v3 v3.3.0
github.com/urfave/cli v1.22.1 // indirect 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/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 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/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 // indirect
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a // indirect golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 // indirect
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
google.golang.org/appengine v1.5.0 // indirect google.golang.org/appengine v1.5.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect

41
go.sum
View file

@ -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-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 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-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 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 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.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 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.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 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= 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 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4= 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 h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4=
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw= 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= 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.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 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 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 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 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= 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.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 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.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 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= 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= 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/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 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 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/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 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= 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-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 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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= 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-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 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-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-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 h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-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 h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-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 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 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-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 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-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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk= google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=

View file

@ -38,7 +38,7 @@ const (
ServiceFrontendurl Key = `service.frontendurl` ServiceFrontendurl Key = `service.frontendurl`
ServiceEnableCaldav Key = `service.enablecaldav` ServiceEnableCaldav Key = `service.enablecaldav`
ServiceRootpath Key = `service.rootpath` ServiceRootpath Key = `service.rootpath`
ServicePageCount Key = `service.pagecount` ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
ServiceEnableMetrics Key = `service.enablemetrics` ServiceEnableMetrics Key = `service.enablemetrics`
ServiceMotd Key = `service.motd` ServiceMotd Key = `service.motd`
ServiceEnableLinkSharing Key = `service.enablelinksharing` ServiceEnableLinkSharing Key = `service.enablelinksharing`
@ -146,7 +146,7 @@ func InitDefaultConfig() {
} }
exPath := filepath.Dir(ex) exPath := filepath.Dir(ex)
ServiceRootpath.setDefault(exPath) ServiceRootpath.setDefault(exPath)
ServicePageCount.setDefault(50) ServiceMaxItemsPerPage.setDefault(50)
ServiceEnableMetrics.setDefault(false) ServiceEnableMetrics.setDefault(false)
ServiceMotd.setDefault("") ServiceMotd.setDefault("")
ServiceEnableLinkSharing.setDefault(true) ServiceEnableLinkSharing.setDefault(true)

View file

@ -124,15 +124,16 @@ func (l *Label) Delete() (err error) {
// @tags labels // @tags labels
// @Accept json // @Accept json
// @Produce 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." // @Param s query string false "Search labels by label text."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.Label "The labels" // @Success 200 {array} models.Label "The labels"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /labels [get] // @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 { if _, is := a.(*LinkSharing); is {
return nil, ErrGenericForbidden{} return nil, 0, 0, ErrGenericForbidden{}
} }
u := &User{ID: a.GetID()} 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 // Get all tasks
taskIDs, err := getUserTaskIDs(u) taskIDs, err := getUserTaskIDs(u)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{ return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
Search: search, Search: search,
User: u, User: u,
TaskIDs: taskIDs, TaskIDs: taskIDs,
Page: page,
PerPage: perPage,
GetUnusedLabels: true, GetUnusedLabels: true,
GroupByLabelIDsOnly: true, GroupByLabelIDsOnly: true,
}) })
@ -198,15 +201,17 @@ func getLabelByIDSimple(labelID int64) (*Label, error) {
func getUserTaskIDs(u *User) (taskIDs []int64, err error) { func getUserTaskIDs(u *User) (taskIDs []int64, err error) {
// Get all lists // Get all lists
lists, err := getRawListsForUser("", u, -1) lists, _, _, err := getRawListsForUser("", u, -1, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tasks, err := getRawTasksForLists(lists, &taskOptions{ tasks, _, _, err := getRawTasksForLists(lists, &taskOptions{
startDate: time.Unix(0, 0), startDate: time.Unix(0, 0),
endDate: time.Unix(0, 0), endDate: time.Unix(0, 0),
sortby: SortTasksByUnsorted, sortby: SortTasksByUnsorted,
page: -1,
perPage: 0,
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -101,21 +101,22 @@ func (lt *LabelTask) Create(a web.Auth) (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param task path int true "Task ID" // @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." // @Param s query string false "Search labels by label text."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.Label "The labels" // @Success 200 {array} models.Label "The labels"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{task}/labels [get] // @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 // Check if the user has the right to see the task
task := Task{ID: lt.TaskID} task := Task{ID: lt.TaskID}
canRead, err := task.CanRead(a) canRead, err := task.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if !canRead { if !canRead {
return nil, ErrNoRightToSeeTask{lt.TaskID, a.GetID()} return nil, 0, 0, ErrNoRightToSeeTask{lt.TaskID, a.GetID()}
} }
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{ return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
@ -137,6 +138,7 @@ type LabelByTaskIDsOptions struct {
User *User User *User
Search string Search string
Page int Page int
PerPage int
TaskIDs []int64 TaskIDs []int64
GetUnusedLabels bool GetUnusedLabels bool
GroupByLabelIDsOnly bool GroupByLabelIDsOnly bool
@ -144,7 +146,7 @@ type LabelByTaskIDsOptions struct {
// Helper function to get all labels for a set of tasks // 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 // 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 // Include unused labels. Needed to be able to show a list of all unused labels a user
// has access to. // has access to.
var uidOrNil interface{} var uidOrNil interface{}
@ -172,10 +174,10 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err
Or(builder.In("label_task.task_id", opts.TaskIDs)). Or(builder.In("label_task.task_id", opts.TaskIDs)).
And("labels.title LIKE ?", "%"+opts.Search+"%"). And("labels.title LIKE ?", "%"+opts.Search+"%").
GroupBy(groupBy). GroupBy(groupBy).
Limit(getLimitFromPageIndex(opts.Page)). Limit(getLimitFromPageIndex(opts.Page, opts.PerPage)).
Find(&labels) Find(&labels)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
// Get all created by users // Get all created by users
@ -186,7 +188,7 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err
users := make(map[int64]*User) users := make(map[int64]*User)
err = x.In("id", userids).Find(&users) err = x.In("id", userids).Find(&users)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
// Obfuscate all user emails // Obfuscate all user emails
@ -199,7 +201,19 @@ func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err
labels[in].CreatedBy = users[l.CreatedByID] 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 // Create or update a bunch of task labels

View file

@ -89,7 +89,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, 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 { if (err != nil) != tt.wantErr {
t.Errorf("LabelTask.ReadAll() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("LabelTask.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
return return

View file

@ -117,7 +117,7 @@ func TestLabel_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, 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 { if (err != nil) != tt.wantErr {
t.Errorf("Label.ReadAll() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("Label.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
return return

View file

@ -136,29 +136,30 @@ func (share *LinkSharing) ReadOne() (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param list path int true "List ID" // @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." // @Param s query string false "Search shares by hash."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.LinkSharing "The share links" // @Success 200 {array} models.LinkSharing "The share links"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{list}/shares [get] // @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} list := &List{ID: share.ListID}
can, err := list.CanRead(a) can, err := list.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if !can { if !can {
return nil, ErrGenericForbidden{} return nil, 0, 0, ErrGenericForbidden{}
} }
var shares []*LinkSharing var shares []*LinkSharing
err = x. err = x.
Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%"). Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%").
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Find(&shares) Find(&shares)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
// Find all users and add them // 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) users := make(map[int64]*User)
err = x.In("id", userIDs).Find(&users) err = x.In("id", userIDs).Find(&users)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
for _, s := range shares { for _, s := range shares {
s.SharedBy = users[s.SharedByID] 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 // Delete removes a link share

View file

@ -77,35 +77,36 @@ func GetListsByNamespaceID(nID int64, doer *User) (lists []*List, err error) {
// @tags list // @tags list
// @Accept json // @Accept json
// @Produce 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." // @Param s query string false "Search lists by title."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.List "The lists" // @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 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists [get] // @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 // Check if we're dealing with a share auth
shareAuth, ok := a.(*LinkSharing) shareAuth, ok := a.(*LinkSharing)
if ok { if ok {
list := &List{ID: shareAuth.ListID} list := &List{ID: shareAuth.ListID}
err := list.GetSimpleByID() err := list.GetSimpleByID()
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
lists := []*List{list} lists := []*List{list}
err = AddListDetails(lists) 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 { if err != nil {
return nil, err return nil, 0, 0, err
} }
// Add more list details // Add more list details
err = AddListDetails(lists) err = AddListDetails(lists)
return lists, err return lists, resultCount, totalItems, err
} }
// ReadOne gets one list by its ID // 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 // 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) fullUser, err := GetUserByID(u.ID)
if err != nil { 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 // 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("ul.user_id = ?", fullUser.ID).
Or("un.user_id = ?", fullUser.ID). Or("un.user_id = ?", fullUser.ID).
GroupBy("l.id"). GroupBy("l.id").
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("l.title LIKE ?", "%"+search+"%"). Where("l.title LIKE ?", "%"+search+"%").
Find(&lists) 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 // AddListDetails adds owner user objects and list tasks to all lists in the slice

View file

@ -36,14 +36,15 @@ func TestList_ReadAll(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
lists2 := List{} lists2 := List{}
lists3, err := lists2.ReadAll("", u, 1) lists3, _, _, err := lists2.ReadAll(u, "", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice)
s := reflect.ValueOf(lists3) s := reflect.ValueOf(lists3)
assert.Equal(t, 16, s.Len()) assert.Equal(t, 16, s.Len())
// Try getting lists for a nonexistant user // 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.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err)) assert.True(t, IsErrUserDoesNotExist(err))
} }

View file

@ -154,22 +154,23 @@ func (tl *TeamList) Delete() (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "List ID" // @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." // @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.TeamWithRight "The teams with their right." // @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 403 {object} code.vikunja.io/web.HTTPError "No right to see the list."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/teams [get] // @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 // Check if the user can read the namespace
l := &List{ID: tl.ListID} l := &List{ID: tl.ListID}
canRead, err := l.CanRead(a) canRead, err := l.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if !canRead { 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 // Get the teams
@ -178,11 +179,24 @@ func (tl *TeamList) ReadAll(search string, a web.Auth, page int) (interface{}, e
Table("teams"). Table("teams").
Join("INNER", "team_list", "team_id = teams.id"). Join("INNER", "team_list", "team_id = teams.id").
Where("team_list.list_id = ?", tl.ListID). Where("team_list.list_id = ?", tl.ListID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("teams.name LIKE ?", "%"+search+"%"). Where("teams.name LIKE ?", "%"+search+"%").
Find(&all) 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 // Update updates a team <-> list relation

View file

@ -69,27 +69,27 @@ func TestTeamList(t *testing.T) {
assert.True(t, IsErrListDoesNotExist(err)) assert.True(t, IsErrListDoesNotExist(err))
// Test Read all // Test Read all
teams, err := tl.ReadAll("", u, 1) teams, _, _, err := tl.ReadAll(u, "", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
s := reflect.ValueOf(teams) s := reflect.ValueOf(teams)
assert.Equal(t, s.Len(), 1) assert.Equal(t, s.Len(), 1)
// Test Read all for nonexistant list // Test Read all for nonexistant list
_, err = tl4.ReadAll("", u, 1) _, _, _, err = tl4.ReadAll(u, "", 1, 50)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrListDoesNotExist(err)) assert.True(t, IsErrListDoesNotExist(err))
// Test Read all for a list where the user is owner of the namespace this list belongs to // Test Read all for a list where the user is owner of the namespace this list belongs to
tl5 := tl tl5 := tl
tl5.ListID = 2 tl5.ListID = 2
_, err = tl5.ReadAll("", u, 1) _, _, _, err = tl5.ReadAll(u, "", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
// Test read all for a list where the user not has access // Test read all for a list where the user not has access
tl6 := tl tl6 := tl
tl6.ListID = 5 tl6.ListID = 5
_, err = tl6.ReadAll("", u, 1) _, _, _, err = tl6.ReadAll(u, "", 1, 50)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrNeedToHaveListReadAccess(err)) assert.True(t, IsErrNeedToHaveListReadAccess(err))

View file

@ -159,22 +159,23 @@ func (lu *ListUser) Delete() (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "List ID" // @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." // @Param s query string false "Search users by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.UserWithRight "The users with the right they have." // @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 403 {object} code.vikunja.io/web.HTTPError "No right to see the list."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /lists/{id}/users [get] // @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 // Check if the user has access to the list
l := &List{ID: lu.ListID} l := &List{ID: lu.ListID}
canRead, err := l.CanRead(a) canRead, err := l.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if !canRead { 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 // Get all users
@ -182,16 +183,25 @@ func (lu *ListUser) ReadAll(search string, a web.Auth, page int) (interface{}, e
err = x. err = x.
Join("INNER", "users_list", "user_id = users.id"). Join("INNER", "users_list", "user_id = users.id").
Where("users_list.list_id = ?", lu.ListID). Where("users_list.list_id = ?", lu.ListID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("users.username LIKE ?", "%"+search+"%"). Where("users.username LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
if err != nil {
return nil, 0, 0, err
}
// Obfuscate all user emails // Obfuscate all user emails
for _, u := range all { for _, u := range all {
u.Email = "" 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 // Update updates a user <-> list relation

View file

@ -203,7 +203,7 @@ func TestListUser_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, 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 { if (err != nil) != tt.wantErr {
t.Errorf("ListUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("ListUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
} }

View file

@ -69,14 +69,18 @@ func SetEngine() (err error) {
return nil 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 // Get everything when page index is -1
if page < 0 { if page < 0 {
return 0, 0 return 0, 0
} }
limit = config.ServicePageCount.GetInt() limit = config.ServiceMaxItemsPerPage.GetInt()
if perPage > 0 {
limit = perPage
}
start = limit * (page - 1) start = limit * (page - 1)
return return
} }

View file

@ -131,20 +131,21 @@ type NamespaceWithLists struct {
// @tags namespace // @tags namespace
// @Accept json // @Accept json
// @Produce 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." // @Param s query string false "Search namespaces by name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.NamespaceWithLists "The Namespaces." // @Success 200 {array} models.NamespaceWithLists "The Namespaces."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces [get] // @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 { if _, is := a.(*LinkSharing); is {
return nil, ErrGenericForbidden{} return nil, 0, 0, ErrGenericForbidden{}
} }
doer, err := getUserWithError(a) doer, err := getUserWithError(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
all := []*NamespaceWithLists{} 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("namespaces.owner_id = ?", doer.ID).
Or("users_namespace.user_id = ?", doer.ID). Or("users_namespace.user_id = ?", doer.ID).
GroupBy("namespaces.id"). GroupBy("namespaces.id").
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("namespaces.name LIKE ?", "%"+search+"%"). Where("namespaces.name LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
if err != nil { if err != nil {
return all, err return all, 0, 0, err
} }
// Get all users // Get all users
@ -187,7 +188,7 @@ func (n *Namespace) ReadAll(search string, a web.Auth, page int) (interface{}, e
Find(&users) Find(&users)
if err != nil { if err != nil {
return all, err return all, 0, 0, err
} }
// Make a list of namespace ids // 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). In("namespace_id", namespaceids).
Find(&lists) Find(&lists)
if err != nil { if err != nil {
return all, err return all, 0, 0, err
} }
// Get all lists individually shared with our user (not via a namespace) // 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"). GroupBy("l.id").
Find(&individualLists) Find(&individualLists)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
// Make the namespace -1 so we now later which one it was // 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 // More details for the lists
AddListDetails(lists) err = AddListDetails(lists)
if err != nil {
return nil, 0, 0, err
}
// Put objects in our namespace list // Put objects in our namespace list
// TODO: Refactor this to use maps for better efficiency
for i, n := range all { for i, n := range all {
// Users // 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 // Create implements the creation method via the interface

View file

@ -139,22 +139,23 @@ func (tn *TeamNamespace) Delete() (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Namespace ID" // @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." // @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.TeamWithRight "The teams with the right they have." // @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 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{id}/teams [get] // @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 // Check if the user can read the namespace
n := Namespace{ID: tn.NamespaceID} n := Namespace{ID: tn.NamespaceID}
canRead, err := n.CanRead(a) canRead, err := n.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if !canRead { 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 // Get the teams
@ -163,11 +164,20 @@ func (tn *TeamNamespace) ReadAll(search string, a web.Auth, page int) (interface
err = x.Table("teams"). err = x.Table("teams").
Join("INNER", "team_namespaces", "team_id = teams.id"). Join("INNER", "team_namespaces", "team_id = teams.id").
Where("team_namespaces.namespace_id = ?", tn.NamespaceID). Where("team_namespaces.namespace_id = ?", tn.NamespaceID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("teams.name LIKE ?", "%"+search+"%"). Where("teams.name LIKE ?", "%"+search+"%").
Find(&all) 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 // Update updates a team <-> namespace relation

View file

@ -68,20 +68,20 @@ func TestTeamNamespace(t *testing.T) {
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))
// Check readall // Check readall
teams, err := tn.ReadAll("", dummyuser, 1) teams, _, _, err := tn.ReadAll(dummyuser, "", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(teams).Kind(), reflect.Slice)
s := reflect.ValueOf(teams) s := reflect.ValueOf(teams)
assert.Equal(t, s.Len(), 1) assert.Equal(t, s.Len(), 1)
// Check readall for a nonexistant namespace // Check readall for a nonexistant namespace
_, err = tn4.ReadAll("", dummyuser, 1) _, _, _, err = tn4.ReadAll(dummyuser, "", 1, 50)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))
// Check with no right to read the namespace // Check with no right to read the namespace
nouser := &User{ID: 393} nouser := &User{ID: 393}
_, err = tn.ReadAll("", nouser, 1) _, _, _, err = tn.ReadAll(nouser, "", 1, 50)
assert.Error(t, err) assert.Error(t, err)
assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err)) assert.True(t, IsErrNeedToHaveNamespaceReadAccess(err))

View file

@ -118,7 +118,7 @@ func TestNamespace_Create(t *testing.T) {
assert.True(t, IsErrNamespaceDoesNotExist(err)) assert.True(t, IsErrNamespaceDoesNotExist(err))
// Get all namespaces of a user // Get all namespaces of a user
nsps, err := n.ReadAll("", doer, 1) nsps, _, _, err := n.ReadAll(doer, "", 1, 50)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(nsps).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(nsps).Kind(), reflect.Slice)
s := reflect.ValueOf(nsps) s := reflect.ValueOf(nsps)

View file

@ -145,22 +145,23 @@ func (nu *NamespaceUser) Delete() (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Namespace ID" // @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." // @Param s query string false "Search users by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.UserWithRight "The users with the right they have." // @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 403 {object} code.vikunja.io/web.HTTPError "No right to see the namespace."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{id}/users [get] // @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 // Check if the user has access to the namespace
l := Namespace{ID: nu.NamespaceID} l := Namespace{ID: nu.NamespaceID}
canRead, err := l.CanRead(a) canRead, err := l.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if !canRead { if !canRead {
return nil, ErrNeedToHaveNamespaceReadAccess{} return nil, 0, 0, ErrNeedToHaveNamespaceReadAccess{}
} }
// Get all users // Get all users
@ -168,16 +169,25 @@ func (nu *NamespaceUser) ReadAll(search string, a web.Auth, page int) (interface
err = x. err = x.
Join("INNER", "users_namespace", "user_id = users.id"). Join("INNER", "users_namespace", "user_id = users.id").
Where("users_namespace.namespace_id = ?", nu.NamespaceID). Where("users_namespace.namespace_id = ?", nu.NamespaceID).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("users.username LIKE ?", "%"+search+"%"). Where("users.username LIKE ?", "%"+search+"%").
Find(&all) Find(&all)
if err != nil {
return nil, 0, 0, err
}
// Obfuscate all user emails // Obfuscate all user emails
for _, u := range all { for _, u := range all {
u.Email = "" 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 // Update updates a user <-> namespace relation

View file

@ -202,7 +202,7 @@ func TestNamespaceUser_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, 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 { if (err != nil) != tt.wantErr {
t.Errorf("NamespaceUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr) t.Errorf("NamespaceUser.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
return return

View file

@ -219,25 +219,26 @@ func (t *Task) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) {
// @tags assignees // @tags assignees
// @Accept json // @Accept json
// @Produce 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 s query string false "Search assignees by their username."
// @Param taskID path int true "Task ID" // @Param taskID path int true "Task ID"
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.User "The assignees" // @Success 200 {array} models.User "The assignees"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{taskID}/assignees [get] // @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) task, err := GetListSimplByTaskID(la.TaskID)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
can, err := task.CanRead(a) can, err := task.CanRead(a)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
if !can { if !can {
return nil, ErrGenericForbidden{} return nil, 0, 0, ErrGenericForbidden{}
} }
var taskAssignees []*User var taskAssignees []*User
@ -245,9 +246,18 @@ func (la *TaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{
Select("users.*"). Select("users.*").
Join("INNER", "users", "task_assignees.user_id = users.id"). Join("INNER", "users", "task_assignees.user_id = users.id").
Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%"). Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%").
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Find(&taskAssignees) 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. // BulkAssignees is a helper struct used to update multiple assignees at once.

View file

@ -100,21 +100,23 @@ func (ta *TaskAttachment) ReadOne() (err error) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Param id path int true "Task ID" // @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 // @Security JWTKeyAuth
// @Success 200 {array} models.TaskAttachment "All attachments for this task" // @Success 200 {array} models.TaskAttachment "All attachments for this task"
// @Failure 403 {object} models.Message "No access to this task." // @Failure 403 {object} models.Message "No access to this task."
// @Failure 404 {object} models.Message "The task does not exist." // @Failure 404 {object} models.Message "The task does not exist."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/{id}/attachments [get] // @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{} attachments := []*TaskAttachment{}
err := x. err = x.
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("task_id = ?", ta.TaskID). Where("task_id = ?", ta.TaskID).
Find(&attachments) Find(&attachments)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
fileIDs := make([]int64, 0, len(attachments)) 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) fs := make(map[int64]*files.File)
err = x.In("id", fileIDs).Find(&fs) err = x.In("id", fileIDs).Find(&fs)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
us := make(map[int64]*User) us := make(map[int64]*User)
err = x.In("id", userIDs).Find(&us) err = x.In("id", userIDs).Find(&us)
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
for _, r := range attachments { 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] 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 // Delete removes an attachment

View file

@ -119,7 +119,7 @@ func TestTaskAttachment_NewAttachment(t *testing.T) {
func TestTaskAttachment_ReadAll(t *testing.T) { func TestTaskAttachment_ReadAll(t *testing.T) {
files.InitTestFileFixtures(t) files.InitTestFileFixtures(t)
ta := &TaskAttachment{TaskID: 1} ta := &TaskAttachment{TaskID: 1}
as, err := ta.ReadAll("", &User{ID: 1}, 0) as, _, _, err := ta.ReadAll(&User{ID: 1}, "", 0, 50)
attachments, _ := as.([]*TaskAttachment) attachments, _ := as.([]*TaskAttachment)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, attachments, 3) assert.Len(t, attachments, 3)

View file

@ -728,7 +728,7 @@ func TestTask_ReadAll(t *testing.T) {
CRUDable: tt.fields.CRUDable, CRUDable: tt.fields.CRUDable,
Rights: tt.fields.Rights, 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 { if (err != nil) != tt.wantErr {
t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr) t.Errorf("Test %s, Task.ReadAll() error = %v, wantErr %v", tt.name, err, tt.wantErr)
return return

View file

@ -123,7 +123,8 @@ const (
// @tags task // @tags task
// @Accept json // @Accept json
// @Produce 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 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 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." // @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" // @Success 200 {array} models.Task "The tasks"
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /tasks/all [get] // @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 var sortby SortBy
switch t.Sorting { switch t.Sorting {
case "priority": case "priority":
@ -156,6 +157,8 @@ func (t *Task) ReadAll(search string, a web.Auth, page int) (interface{}, error)
sortby: sortby, sortby: sortby,
startDate: time.Unix(t.StartDateSortUnix, 0), startDate: time.Unix(t.StartDateSortUnix, 0),
endDate: time.Unix(t.EndDateSortUnix, 0), endDate: time.Unix(t.EndDateSortUnix, 0),
page: page,
perPage: perPage,
} }
shareAuth, is := a.(*LinkSharing) 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} list := &List{ID: shareAuth.ListID}
err := list.GetSimpleByID() err := list.GetSimpleByID()
if err != nil { if err != nil {
return nil, err return nil, 0, 0, err
} }
return getTasksForLists([]*List{list}, taskopts) return getTasksForLists([]*List{list}, taskopts)
} }
// Get all lists for the user // 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 { if err != nil {
return nil, err return nil, 0, 0, err
} }
return getTasksForLists(lists, taskopts) return getTasksForLists(lists, taskopts)
@ -182,9 +185,11 @@ type taskOptions struct {
sortby SortBy sortby SortBy
startDate time.Time startDate time.Time
endDate 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 // Get all list IDs and get the tasks
var listIDs []int64 var listIDs []int64
@ -219,42 +224,62 @@ func getRawTasksForLists(lists []*List, opts *taskOptions) (taskMap map[int64]*T
endDateUnix = opts.endDate.Unix() endDateUnix = opts.endDate.Unix()
} }
if err := x.In("list_id", listIDs). err := x.In("list_id", listIDs).
Where("text LIKE ?", "%"+opts.search+"%"). Where("text LIKE ?", "%"+opts.search+"%").
And("((due_date_unix BETWEEN ? AND ?) OR "+ And("((due_date_unix BETWEEN ? AND ?) OR "+
"(start_date_unix BETWEEN ? and ?) OR "+ "(start_date_unix BETWEEN ? and ?) OR "+
"(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix). "(end_date_unix BETWEEN ? and ?))", startDateUnix, endDateUnix, startDateUnix, endDateUnix, startDateUnix, endDateUnix).
OrderBy(orderby). OrderBy(orderby).
Find(&taskMap); err != nil { Limit(getLimitFromPageIndex(opts.page, opts.perPage)).
return nil, err 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 { } else {
if err := x.In("list_id", listIDs). err := x.In("list_id", listIDs).
Where("text LIKE ?", "%"+opts.search+"%"). Where("text LIKE ?", "%"+opts.search+"%").
OrderBy(orderby). OrderBy(orderby).
Find(&taskMap); err != nil { Limit(getLimitFromPageIndex(opts.page, opts.perPage)).
return nil, err 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 { if err != nil {
return nil, err return nil, 0, 0, err
} }
tasks, err = addMoreInfoToTasks(taskMap) tasks, err = addMoreInfoToTasks(taskMap)
if err != nil { 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) // 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. // we have to manually sort the tasks again here.
sortTasks(tasks, opts.sortby) sortTasks(tasks, opts.sortby)
return tasks, err return tasks, resultCount, totalItems, err
} }
func sortTasks(tasks []*Task, by SortBy) { func sortTasks(tasks []*Task, by SortBy) {
@ -339,7 +364,7 @@ func GetTaskByID(listTaskID int64) (listTask Task, err error) {
} }
// Get task labels // Get task labels
taskLabels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{ taskLabels, _, _, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{
TaskIDs: []int64{listTaskID}, TaskIDs: []int64{listTaskID},
}) })
if err != nil { if err != nil {
@ -413,7 +438,7 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (tasks []*Task, err error) {
} }
// Get all labels for all the tasks // Get all labels for all the tasks
labels, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{ labels, _, _, err := getLabelsByTaskIDs(&LabelByTaskIDsOptions{
TaskIDs: taskIDs, TaskIDs: taskIDs,
Page: -1, Page: -1,
}) })

View file

@ -135,27 +135,37 @@ func (t *Team) ReadOne() (err error) {
// @tags team // @tags team
// @Accept json // @Accept json
// @Produce 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." // @Param s query string false "Search teams by its name."
// @Security JWTKeyAuth // @Security JWTKeyAuth
// @Success 200 {array} models.Team "The teams." // @Success 200 {array} models.Team "The teams."
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /teams [get] // @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 { if _, is := a.(*LinkSharing); is {
return nil, ErrGenericForbidden{} return nil, 0, 0, ErrGenericForbidden{}
} }
all := []*Team{} all := []*Team{}
err := x.Select("teams.*"). err = x.Select("teams.*").
Table("teams"). Table("teams").
Join("INNER", "team_members", "team_members.team_id = teams.id"). Join("INNER", "team_members", "team_members.team_id = teams.id").
Where("team_members.user_id = ?", a.GetID()). Where("team_members.user_id = ?", a.GetID()).
Limit(getLimitFromPageIndex(page)). Limit(getLimitFromPageIndex(page, perPage)).
Where("teams.name LIKE ?", "%"+search+"%"). Where("teams.name LIKE ?", "%"+search+"%").
Find(&all) 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 // Create is the handler to create a team

View file

@ -55,7 +55,7 @@ func TestTeam_Create(t *testing.T) {
assert.True(t, IsErrTeamDoesNotExist(err)) assert.True(t, IsErrTeamDoesNotExist(err))
// Get all teams the user is part of // 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.NoError(t, err)
assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice) assert.Equal(t, reflect.TypeOf(ts).Kind(), reflect.Slice)
s := reflect.ValueOf(ts) s := reflect.ValueOf(ts)

View file

@ -89,7 +89,7 @@ func (vcls *VikunjaCaldavListStorage) GetResources(rpath string, withChildren bo
} }
// Otherwise get all lists // Otherwise get all lists
thelists, err := vcls.list.ReadAll("", vcls.user, -1) thelists, _, _, err := vcls.list.ReadAll(vcls.user, "", -1, 50)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -16,6 +16,11 @@
// @title Vikunja API // @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. <!-- ReDoc-Inject: <security-definitions> --> // @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. <!-- ReDoc-Inject: <security-definitions> -->
// @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 # Authorization
// @description **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully. // @description **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.
// @description // @description
@ -110,6 +115,7 @@ func NewEcho() *echo.Echo {
AuthObject: apiv1.GetAuthFromClaims, AuthObject: apiv1.GetAuthFromClaims,
}) })
handler.SetLoggingProvider(log.GetLogger()) handler.SetLoggingProvider(log.GetLogger())
handler.SetMaxItemsPerPage(config.ServiceMaxItemsPerPage.GetInt())
return e return e
} }

View file

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at // 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 package swagger
@ -75,7 +75,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -353,7 +359,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -706,7 +718,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -839,7 +857,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1230,7 +1254,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1584,7 +1614,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1855,7 +1891,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1988,7 +2030,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -2528,7 +2576,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -2773,6 +2827,18 @@ var doc = `{
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "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": { "responses": {
@ -3007,7 +3073,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -3424,7 +3496,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -3606,7 +3684,13 @@ var doc = `{
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -5245,7 +5329,7 @@ var doc = `{
"type": "boolean" "type": "boolean"
}, },
"max_file_size": { "max_file_size": {
"type": "integer" "type": "string"
}, },
"motd": { "motd": {
"type": "string" "type": "string"
@ -5284,7 +5368,7 @@ var SwaggerInfo = swaggerInfo{
BasePath: "/api/v1", BasePath: "/api/v1",
Schemes: []string{}, Schemes: []string{},
Title: "Vikunja API", 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. <!-- ReDoc-Inject: <security-definitions> -->\n# Authorization\n**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n<!-- ReDoc-Inject: <security-definitions> -->", 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 <jwt-token>`-header to authenticate successfully.\n\n**BasicAuth:** Only used when requesting tasks via caldav.\n<!-- ReDoc-Inject: <security-definitions> -->",
} }
type s struct{} type s struct{}

View file

@ -1,7 +1,7 @@
{ {
"swagger": "2.0", "swagger": "2.0",
"info": { "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", "title": "Vikunja API",
"contact": { "contact": {
"name": "General Vikunja contact", "name": "General Vikunja contact",
@ -57,7 +57,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -335,7 +341,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -688,7 +700,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -821,7 +839,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1212,7 +1236,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1566,7 +1596,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1837,7 +1873,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -1970,7 +2012,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -2510,7 +2558,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -2755,6 +2809,18 @@
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true "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": { "responses": {
@ -2989,7 +3055,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -3406,7 +3478,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -3588,7 +3666,13 @@
{ {
"type": "integer", "type": "integer",
"description": "The page number. Used for pagination. If not provided, the first page of results is returned.", "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" "in": "query"
}, },
{ {
@ -5226,7 +5310,7 @@
"type": "boolean" "type": "boolean"
}, },
"max_file_size": { "max_file_size": {
"type": "integer" "type": "string"
}, },
"motd": { "motd": {
"type": "string" "type": "string"

View file

@ -833,7 +833,7 @@ definitions:
link_sharing_enabled: link_sharing_enabled:
type: boolean type: boolean
max_file_size: max_file_size:
type: integer type: string
motd: motd:
type: string type: string
version: version:
@ -845,7 +845,10 @@ info:
name: General Vikunja contact name: General Vikunja contact
url: http://vikunja.io/en/contact/ url: http://vikunja.io/en/contact/
description: |- 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. <!-- ReDoc-Inject: <security-definitions> --> # 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 # Authorization
**JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully. **JWT-Auth:** Main authorization method, used for most of the requests. Needs `Authorization: Bearer <jwt-token>`-header to authenticate successfully.
@ -880,7 +883,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search labels by label text. - description: Search labels by label text.
in: query in: query
@ -1061,7 +1069,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search lists by title. - description: Search lists by title.
in: query in: query
@ -1290,7 +1303,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search teams by its name. - description: Search teams by its name.
in: query in: query
@ -1377,7 +1395,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search users by its name. - description: Search users by its name.
in: query in: query
@ -1464,7 +1487,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search shares by hash. - description: Search shares by hash.
in: query in: query
@ -1868,7 +1896,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search namespaces by name. - description: Search namespaces by name.
in: query in: query
@ -2043,7 +2076,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search teams by its name. - description: Search teams by its name.
in: query in: query
@ -2131,7 +2169,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search users by its name. - description: Search users by its name.
in: query in: query
@ -2567,6 +2610,16 @@ paths:
name: id name: id
required: true required: true
type: integer 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: produces:
- application/json - application/json
responses: responses:
@ -2724,7 +2777,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search labels by label text. - description: Search labels by label text.
in: query in: query
@ -2844,7 +2902,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search assignees by their username. - description: Search assignees by their username.
in: query in: query
@ -3119,7 +3182,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search tasks by task text. - description: Search tasks by task text.
in: query in: query
@ -3209,7 +3277,12 @@ paths:
- description: The page number. Used for pagination. If not provided, the first - description: The page number. Used for pagination. If not provided, the first
page of results is returned. page of results is returned.
in: query 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 type: integer
- description: Search teams by its name. - description: Search teams by its name.
in: query in: query

36
vendor/code.vikunja.io/web/Readme.md generated vendored
View file

@ -60,7 +60,7 @@ This interface defines methods to Create/Read/ReadAll/Update/Delete something. I
type CRUDable interface { type CRUDable interface {
Create(Auth) error Create(Auth) error
ReadOne() 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 Update() error
Delete() 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 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. 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 #### Full Example
```go ```go
@ -137,18 +144,35 @@ handler.SetLoggingProvider(&log.Log)
### Pagination ### Pagination
When using the `ReadAll`-method, the third parameter contains the requested page. The `ReadAll`-method has a number of parameters:
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 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 ### Search
When using the `ReadAll`-method, the first parameter is a search term which should be used to search items of your struct. 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. 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 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. the decision to design the function like this, in order to keep the places with mostly the same logic as few as possible.

View file

@ -21,9 +21,11 @@ import (
"github.com/op/go-logging" "github.com/op/go-logging"
) )
// Config contains the config for the web handler
type Config struct { type Config struct {
AuthProvider *web.Auths AuthProvider *web.Auths
LoggingProvider *logging.Logger LoggingProvider *logging.Logger
MaxItemsPerPage int
} }
var config *Config var config *Config
@ -32,10 +34,17 @@ func init() {
config = &Config{} config = &Config{}
} }
// SetAuthProvider sets the auth provider in config
func SetAuthProvider(provider *web.Auths) { func SetAuthProvider(provider *web.Auths) {
config.AuthProvider = provider config.AuthProvider = provider
} }
// SetLoggingProvider sets the logging provider in the config
func SetLoggingProvider(logger *logging.Logger) { func SetLoggingProvider(logger *logging.Logger) {
config.LoggingProvider = logger config.LoggingProvider = logger
} }
// SetMaxItemsPerPage sets the max number of items per page in the config
func SetMaxItemsPerPage(maxItemsPerPage int) {
config.MaxItemsPerPage = maxItemsPerPage
}

View file

@ -17,6 +17,7 @@ package handler
import ( import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"math"
"net/http" "net/http"
"strconv" "strconv"
) )
@ -47,16 +48,56 @@ func (c *WebHandler) ReadAllWeb(ctx echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.") return echo.NewHTTPError(http.StatusBadRequest, "Bad page requested.")
} }
if pageNumber < 0 { 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
search := ctx.QueryParam("s") search := ctx.QueryParam("s")
lists, err := currentStruct.ReadAll(search, currentAuth, pageNumber) result, resultCount, numberOfItems, err := currentStruct.ReadAll(currentAuth, search, pageNumber, perPageNumber)
if err != nil { if err != nil {
return HandleHTTPError(err, ctx) 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)
} }

2
vendor/code.vikunja.io/web/web.go generated vendored
View file

@ -31,7 +31,7 @@ type Rights interface {
type CRUDable interface { type CRUDable interface {
Create(Auth) error Create(Auth) error
ReadOne() 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 Update() error
Delete() error Delete() error
} }

View file

@ -43,16 +43,12 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
if err := b.bindData(i, params, "param"); err != nil { if err := b.bindData(i, params, "param"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) 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 { if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err) return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
} }
if req.ContentLength == 0 {
return return
} }
return NewHTTPError(http.StatusBadRequest, "Request body can't be empty")
}
ctype := req.Header.Get(HeaderContentType) ctype := req.Header.Get(HeaderContentType)
switch { switch {
case strings.HasPrefix(ctype, MIMEApplicationJSON): case strings.HasPrefix(ctype, MIMEApplicationJSON):
@ -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 { 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 return nil
} }
typ := reflect.TypeOf(ptr).Elem() typ := reflect.TypeOf(ptr).Elem()
val := reflect.ValueOf(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 { if typ.Kind() != reflect.Struct {
return errors.New("binding element must be a struct") return errors.New("binding element must be a struct")
} }

View file

@ -26,6 +26,9 @@ type (
// SetRequest sets `*http.Request`. // SetRequest sets `*http.Request`.
SetRequest(r *http.Request) SetRequest(r *http.Request)
// SetResponse sets `*Response`.
SetResponse(r *Response)
// Response returns `*Response`. // Response returns `*Response`.
Response() *Response Response() *Response
@ -228,6 +231,10 @@ func (c *context) Response() *Response {
return c.response return c.response
} }
func (c *context) SetResponse(r *Response) {
c.response = r
}
func (c *context) IsTLS() bool { func (c *context) IsTLS() bool {
return c.request.TLS != nil return c.request.TLS != nil
} }

View file

@ -99,9 +99,9 @@ type (
// HTTPError represents an error that occurred while handling a request. // HTTPError represents an error that occurred while handling a request.
HTTPError struct { HTTPError struct {
Code int Code int `json:"-"`
Message interface{} Message interface{} `json:"message"`
Internal error // Stores the error returned by an external dependency Internal error `json:"-"` // Stores the error returned by an external dependency
} }
// MiddlewareFunc defines a function to process middleware. // MiddlewareFunc defines a function to process middleware.
@ -222,11 +222,12 @@ const (
HeaderContentSecurityPolicy = "Content-Security-Policy" HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only" HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
HeaderXCSRFToken = "X-CSRF-Token" HeaderXCSRFToken = "X-CSRF-Token"
HeaderReferrerPolicy = "Referrer-Policy"
) )
const ( const (
// Version of Echo // Version of Echo
Version = "4.1.5" Version = "4.1.11"
website = "https://echo.labstack.com" website = "https://echo.labstack.com"
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo // http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
banner = ` banner = `
@ -340,32 +341,31 @@ func (e *Echo) Routers() map[string]*Router {
// DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response // DefaultHTTPErrorHandler is the default HTTP error handler. It sends a JSON response
// with status code. // with status code.
func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) { func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
var ( he, ok := err.(*HTTPError)
code = http.StatusInternalServerError if ok {
msg interface{}
)
if he, ok := err.(*HTTPError); ok {
code = he.Code
msg = he.Message
if he.Internal != nil { 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 { } 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 // Send response
if !c.Response().Committed { if !c.Response().Committed {
if c.Request().Method == http.MethodHead { // Issue #608 if c.Request().Method == http.MethodHead { // Issue #608
err = c.NoContent(code) err = c.NoContent(he.Code)
} else { } else {
err = c.JSON(code, msg) err = c.JSON(he.Code, he.Message)
} }
if err != nil { if err != nil {
e.Logger.Error(err) e.Logger.Error(err)
@ -748,7 +748,7 @@ func NewHTTPError(code int, message ...interface{}) *HTTPError {
// Error makes it compatible with `error` interface. // Error makes it compatible with `error` interface.
func (he *HTTPError) Error() string { 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 // 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) { return func(c Context) (err error) {
m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { m(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c.SetRequest(r) c.SetRequest(r)
c.SetResponse(NewResponse(w, c.Echo()))
err = next(c) err = next(c)
})).ServeHTTP(c.Response(), c.Request()) })).ServeHTTP(c.Response(), c.Request())
return return

View file

@ -4,12 +4,8 @@ go 1.12
require ( require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/labstack/gommon v0.2.9 github.com/labstack/gommon v0.3.0
github.com/stretchr/testify v1.3.0 github.com/stretchr/testify v1.4.0
github.com/valyala/fasttemplate v1.0.1 github.com/valyala/fasttemplate v1.0.1
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
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
) )

View file

@ -1,55 +1,34 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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.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 h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 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.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
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/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 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.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.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 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/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 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 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-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-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
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/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-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-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-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-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=

View file

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"io" "io"
"os"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -74,7 +73,6 @@ var (
`"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` + `"status":${status},"error":"${error}","latency":${latency},"latency_human":"${latency_human}"` +
`,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n", `,"bytes_in":${bytes_in},"bytes_out":${bytes_out}}` + "\n",
CustomTimeFormat: "2006-01-02 15:04:05.00000", CustomTimeFormat: "2006-01-02 15:04:05.00000",
Output: os.Stdout,
colorer: color.New(), colorer: color.New(),
} }
) )
@ -214,6 +212,10 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
return return
} }
if config.Output == nil {
_, err = c.Logger().Output().Write(buf.Bytes())
return
}
_, err = config.Output.Write(buf.Bytes()) _, err = config.Output.Write(buf.Bytes())
return return
} }

View file

@ -92,15 +92,14 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
in, _, err := c.Response().Hijack() in, _, err := c.Response().Hijack()
if err != nil { 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 return
} }
defer in.Close() defer in.Close()
out, err := net.Dial("tcp", t.URL.Host) out, err := net.Dial("tcp", t.URL.Host)
if err != nil { if err != nil {
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err)) c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, dial error=%v, url=%s", t.URL, err)))
c.Error(he)
return return
} }
defer out.Close() defer out.Close()
@ -108,8 +107,7 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
// Write header // Write header
err = r.Write(out) err = r.Write(out)
if err != nil { if err != nil {
he := echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err)) c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("proxy raw, request header copy error=%v, url=%s", t.URL, err)))
c.Error(he)
return return
} }
@ -123,7 +121,7 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
go cp(in, out) go cp(in, out)
err = <-errCh err = <-errCh
if err != nil && err != io.EOF { 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: default:
proxyHTTP(tgt, c, config).ServeHTTP(res, req) proxyHTTP(tgt, c, config).ServeHTTP(res, req)
} }
if e, ok := c.Get("_error").(error); ok {
err = e
}
return return
} }

View file

@ -17,8 +17,7 @@ func proxyHTTP(tgt *ProxyTarget, c echo.Context, config ProxyConfig) http.Handle
if tgt.Name != "" { if tgt.Name != "" {
desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String()) desc = fmt.Sprintf("%s(%s)", tgt.Name, tgt.URL.String())
} }
c.Logger().Errorf("remote %s unreachable, could not forward: %v", desc, err) c.Set("_error", echo.NewHTTPError(http.StatusBadGateway, fmt.Sprintf("remote %s unreachable, could not forward: %v", desc, err)))
c.Error(echo.NewHTTPError(http.StatusServiceUnavailable))
} }
proxy.Transport = config.Transport proxy.Transport = config.Transport
return proxy return proxy

View file

@ -66,6 +66,11 @@ type (
// maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/ // maintained by Chrome (and used by Firefox and Safari): https://hstspreload.org/
// Optional. Default value false. // Optional. Default value false.
HSTSPreloadEnabled bool `yaml:"hsts_preload_enabled"` 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) res.Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
} }
} }
if config.ReferrerPolicy != "" {
res.Header().Set(echo.HeaderReferrerPolicy, config.ReferrerPolicy)
}
return next(c) return next(c)
} }
} }

View file

@ -336,10 +336,14 @@ func (r *Router) Find(method, path string, c Context) {
} }
} }
if l == pl { if l == pl {
// Continue search // Continue search
search = search[l:] search = search[l:]
} else { } else {
if nn == nil { // Issue #1348
return // Not found
}
cn = nn cn = nn
search = ns search = ns
if nk == pkind { if nk == pkind {
@ -347,8 +351,6 @@ func (r *Router) Find(method, path string, c Context) {
} else if nk == akind { } else if nk == akind {
goto Any goto Any
} }
// Not found
return
} }
if search == "" { if search == "" {
@ -398,6 +400,9 @@ func (r *Router) Find(method, path string, c Context) {
if nn != nil { if nn != nil {
cn = nn cn = nn
nn = cn.parent // Next (Issue #954) nn = cn.parent // Next (Issue #954)
if nn != nil {
nk = nn.kind
}
search = ns search = ns
if nk == pkind { if nk == pkind {
goto Param goto Param
@ -405,8 +410,7 @@ func (r *Router) Find(method, path string, c Context) {
goto Any goto Any
} }
} }
// Not found return // Not found
return
} }
pvalues[len(cn.pnames)-1] = search pvalues[len(cn.pnames)-1] = search
break break

View file

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"regexp" "regexp"
"strconv" "strconv"
"strings"
) )
type ( type (
@ -73,7 +74,7 @@ func (*Bytes) Parse(value string) (i int64, err error) {
return 0, fmt.Errorf("error parsing value=%s", value) return 0, fmt.Errorf("error parsing value=%s", value)
} }
bytesString := parts[1] bytesString := parts[1]
multiple := parts[2] multiple := strings.ToUpper(parts[2])
bytes, err := strconv.ParseFloat(bytesString, 64) bytes, err := strconv.ParseFloat(bytesString, 64)
if err != nil { if err != nil {
return return

View file

@ -9,7 +9,7 @@ import (
_ "github.com/mattn/go-isatty" _ "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 { func NewColorable(file *os.File) io.Writer {
if file == nil { if file == nil {
panic("nil passed instead of *os.File to NewColorable()") panic("nil passed instead of *os.File to NewColorable()")
@ -18,12 +18,12 @@ func NewColorable(file *os.File) io.Writer {
return file 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 { func NewColorableStdout() io.Writer {
return os.Stdout 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 { func NewColorableStderr() io.Writer {
return os.Stderr return os.Stderr
} }

View file

@ -10,7 +10,7 @@ import (
_ "github.com/mattn/go-isatty" _ "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 { func NewColorable(file *os.File) io.Writer {
if file == nil { if file == nil {
panic("nil passed instead of *os.File to NewColorable()") panic("nil passed instead of *os.File to NewColorable()")
@ -19,12 +19,12 @@ func NewColorable(file *os.File) io.Writer {
return file 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 { func NewColorableStdout() io.Writer {
return os.Stdout 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 { func NewColorableStderr() io.Writer {
return os.Stderr return os.Stderr
} }

View file

@ -81,7 +81,7 @@ var (
procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
) )
// Writer provide colorable Writer to the console // Writer provides colorable Writer to the console
type Writer struct { type Writer struct {
out io.Writer out io.Writer
handle syscall.Handle handle syscall.Handle
@ -91,7 +91,7 @@ type Writer struct {
rest bytes.Buffer 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 { func NewColorable(file *os.File) io.Writer {
if file == nil { if file == nil {
panic("nil passed instead of *os.File to NewColorable()") panic("nil passed instead of *os.File to NewColorable()")
@ -106,12 +106,12 @@ func NewColorable(file *os.File) io.Writer {
return file 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 { func NewColorableStdout() io.Writer {
return NewColorable(os.Stdout) 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 { func NewColorableStderr() io.Writer {
return NewColorable(os.Stderr) return NewColorable(os.Stderr)
} }
@ -414,7 +414,15 @@ func doTitleSequence(er *bytes.Reader) error {
return nil 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) { func (w *Writer) Write(data []byte) (n int, err error) {
var csbi consoleScreenBufferInfo var csbi consoleScreenBufferInfo
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
@ -500,7 +508,7 @@ loop:
switch m { switch m {
case 'A': case 'A':
n, err = strconv.Atoi(buf.String()) n, err = atoiWithDefault(buf.String(), 1)
if err != nil { if err != nil {
continue continue
} }
@ -508,7 +516,7 @@ loop:
csbi.cursorPosition.y -= short(n) csbi.cursorPosition.y -= short(n)
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'B': case 'B':
n, err = strconv.Atoi(buf.String()) n, err = atoiWithDefault(buf.String(), 1)
if err != nil { if err != nil {
continue continue
} }
@ -516,7 +524,7 @@ loop:
csbi.cursorPosition.y += short(n) csbi.cursorPosition.y += short(n)
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'C': case 'C':
n, err = strconv.Atoi(buf.String()) n, err = atoiWithDefault(buf.String(), 1)
if err != nil { if err != nil {
continue continue
} }
@ -524,7 +532,7 @@ loop:
csbi.cursorPosition.x += short(n) csbi.cursorPosition.x += short(n)
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
case 'D': case 'D':
n, err = strconv.Atoi(buf.String()) n, err = atoiWithDefault(buf.String(), 1)
if err != nil { if err != nil {
continue continue
} }
@ -557,6 +565,9 @@ loop:
if err != nil { if err != nil {
continue continue
} }
if n < 1 {
n = 1
}
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
csbi.cursorPosition.x = short(n - 1) csbi.cursorPosition.x = short(n - 1)
procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) 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))) 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))) 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': case 'm':
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
attr := csbi.attributes attr := csbi.attributes

View file

@ -1,6 +1,4 @@
github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw= 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.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 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=

View file

@ -5,17 +5,17 @@ import (
"io" "io"
) )
// NonColorable hold writer but remove escape sequence. // NonColorable holds writer but removes escape sequence.
type NonColorable struct { type NonColorable struct {
out io.Writer 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 { func NewNonColorable(w io.Writer) io.Writer {
return &NonColorable{out: w} return &NonColorable{out: w}
} }
// Write write data on console // Write writes data on console
func (w *NonColorable) Write(data []byte) (n int, err error) { func (w *NonColorable) Write(data []byte) (n int, err error) {
er := bytes.NewReader(data) er := bytes.NewReader(data)
var bw [1]byte var bw [1]byte

View file

@ -3,7 +3,7 @@ fasttemplate
Simple and fast template engine for Go. 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 :) 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. Take a look at [quicktemplate](https://github.com/valyala/quicktemplate) if you need fast yet powerful html template engine.

16
vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go generated vendored Normal file
View file

@ -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)
}

View file

@ -17,7 +17,7 @@ func UnixCredentials(ucred *Ucred) []byte {
h.Level = SOL_SOCKET h.Level = SOL_SOCKET
h.Type = SCM_CREDENTIALS h.Type = SCM_CREDENTIALS
h.SetLen(CmsgLen(SizeofUcred)) h.SetLen(CmsgLen(SizeofUcred))
*((*Ucred)(cmsgData(h))) = *ucred *(*Ucred)(h.data(0)) = *ucred
return b return b
} }

View file

@ -9,35 +9,9 @@
package unix package unix
import ( import (
"runtime"
"unsafe" "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 // CmsgLen returns the value to store in the Len field of the Cmsghdr
// structure, taking into account any necessary alignment. // structure, taking into account any necessary alignment.
func CmsgLen(datalen int) int { func CmsgLen(datalen int) int {
@ -50,8 +24,8 @@ func CmsgSpace(datalen int) int {
return cmsgAlignOf(SizeofCmsghdr) + cmsgAlignOf(datalen) return cmsgAlignOf(SizeofCmsghdr) + cmsgAlignOf(datalen)
} }
func cmsgData(h *Cmsghdr) unsafe.Pointer { func (h *Cmsghdr) data(offset uintptr) unsafe.Pointer {
return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(SizeofCmsghdr))) return unsafe.Pointer(uintptr(unsafe.Pointer(h)) + uintptr(cmsgAlignOf(SizeofCmsghdr)) + offset)
} }
// SocketControlMessage represents a socket control message. // SocketControlMessage represents a socket control message.
@ -94,10 +68,8 @@ func UnixRights(fds ...int) []byte {
h.Level = SOL_SOCKET h.Level = SOL_SOCKET
h.Type = SCM_RIGHTS h.Type = SCM_RIGHTS
h.SetLen(CmsgLen(datalen)) h.SetLen(CmsgLen(datalen))
data := cmsgData(h) for i, fd := range fds {
for _, fd := range fds { *(*int32)(h.data(4 * uintptr(i))) = int32(fd)
*(*int32)(data) = int32(fd)
data = unsafe.Pointer(uintptr(data) + 4)
} }
return b return b
} }

38
vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go generated vendored Normal file
View file

@ -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)
}

View file

@ -237,7 +237,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
break 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) sa.Name = string(bytes)
return sa, nil return sa, nil

View file

@ -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 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 { func Uname(uname *Utsname) error {
mib := []_C_int{CTL_KERN, KERN_OSTYPE} mib := []_C_int{CTL_KERN, KERN_OSTYPE}
n := unsafe.Sizeof(uname.Sysname) n := unsafe.Sizeof(uname.Sysname)

View file

@ -10,7 +10,6 @@ import (
"syscall" "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) //sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error)
func setTimespec(sec, nsec int64) Timespec { func setTimespec(sec, nsec int64) Timespec {

View file

@ -10,7 +10,6 @@ import (
"syscall" "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) //sys ptrace(request int, pid int, addr uintptr, data uintptr) (err error)
func setTimespec(sec, nsec int64) Timespec { func setTimespec(sec, nsec int64) Timespec {

View file

@ -12,10 +12,6 @@ func ptrace(request int, pid int, addr uintptr, data uintptr) error {
return ENOTSUP return ENOTSUP
} }
func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
return ENOTSUP
}
func setTimespec(sec, nsec int64) Timespec { func setTimespec(sec, nsec int64) Timespec {
return Timespec{Sec: int32(sec), Nsec: int32(nsec)} return Timespec{Sec: int32(sec), Nsec: int32(nsec)}
} }

View file

@ -14,10 +14,6 @@ func ptrace(request int, pid int, addr uintptr, data uintptr) error {
return ENOTSUP return ENOTSUP
} }
func sysctl(mib []_C_int, old *byte, oldlen *uintptr, new *byte, newlen uintptr) error {
return ENOTSUP
}
func setTimespec(sec, nsec int64) Timespec { func setTimespec(sec, nsec int64) Timespec {
return Timespec{Sec: sec, Nsec: nsec} return Timespec{Sec: sec, Nsec: nsec}
} }

View file

@ -12,9 +12,25 @@
package unix 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. // SockaddrDatalink implements the Sockaddr interface for AF_LINK type sockets.
type SockaddrDatalink struct { 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 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 { func sysctlUname(mib []_C_int, old *byte, oldlen *uintptr) error {
err := sysctl(mib, old, oldlen, nil, 0) err := sysctl(mib, old, oldlen, nil, 0)
if err != nil { if err != nil {

View file

@ -36,8 +36,6 @@ var (
// INO64_FIRST from /usr/src/lib/libc/sys/compat-ino64.h // INO64_FIRST from /usr/src/lib/libc/sys/compat-ino64.h
const _ino64First = 1200031 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 { func supportsABI(ver uint32) bool {
osreldateOnce.Do(func() { osreldate, _ = SysctlUint32("kern.osreldate") }) osreldateOnce.Do(func() { osreldate, _ = SysctlUint32("kern.osreldate") })
return osreldate >= ver 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 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 { func Uname(uname *Utsname) error {
mib := []_C_int{CTL_KERN, KERN_OSTYPE} mib := []_C_int{CTL_KERN, KERN_OSTYPE}
n := unsafe.Sizeof(uname.Sysname) n := unsafe.Sizeof(uname.Sysname)

View file

@ -884,7 +884,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
for n < len(pp.Path) && pp.Path[n] != 0 { for n < len(pp.Path) && pp.Path[n] != 0 {
n++ 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) sa.Name = string(bytes)
return sa, nil return sa, nil

View file

@ -18,8 +18,6 @@ import (
"unsafe" "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. // SockaddrDatalink implements the Sockaddr interface for AF_LINK type sockets.
type SockaddrDatalink struct { type SockaddrDatalink struct {
Len uint8 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 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) { func IoctlGetPtmget(fd int, req uint) (*Ptmget, error) {
var value Ptmget var value Ptmget
err := ioctl(fd, req, uintptr(unsafe.Pointer(&value))) err := ioctl(fd, req, uintptr(unsafe.Pointer(&value)))

View file

@ -18,8 +18,6 @@ import (
"unsafe" "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. // SockaddrDatalink implements the Sockaddr interface for AF_LINK type sockets.
type SockaddrDatalink struct { type SockaddrDatalink struct {
Len uint8 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 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) //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) { func Ppoll(fds []PollFd, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {

View file

@ -391,7 +391,7 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
for n < len(pp.Path) && pp.Path[n] != 0 { for n < len(pp.Path) && pp.Path[n] != 0 {
n++ 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) sa.Name = string(bytes)
return sa, nil return sa, nil

View file

@ -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 // 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) { 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) _, _, 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 { 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 // 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) { 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) _, _, e1 := Syscall6(SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
if e1 != 0 { if e1 != 0 {

View file

@ -757,6 +757,27 @@ func libc_ioctl_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT // 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) { 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) _, _, 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 { 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 // 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) { 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) _, _, e1 := syscall_syscall6(funcPC(libc_ptrace_trampoline), uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
if e1 != 0 { if e1 != 0 {

View file

@ -40,6 +40,8 @@ TEXT ·libc_sendmsg_trampoline(SB),NOSPLIT,$0-0
JMP libc_sendmsg(SB) JMP libc_sendmsg(SB)
TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0
JMP libc_kevent(SB) JMP libc_kevent(SB)
TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0
JMP libc___sysctl(SB)
TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0
JMP libc_utimes(SB) JMP libc_utimes(SB)
TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0
@ -104,8 +106,6 @@ TEXT ·libc_chown_trampoline(SB),NOSPLIT,$0-0
JMP libc_chown(SB) JMP libc_chown(SB)
TEXT ·libc_chroot_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_chroot_trampoline(SB),NOSPLIT,$0-0
JMP libc_chroot(SB) 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 TEXT ·libc_close_trampoline(SB),NOSPLIT,$0-0
JMP libc_close(SB) JMP libc_close(SB)
TEXT ·libc_dup_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_dup_trampoline(SB),NOSPLIT,$0-0
@ -262,8 +262,6 @@ TEXT ·libc_mmap_trampoline(SB),NOSPLIT,$0-0
JMP libc_mmap(SB) JMP libc_mmap(SB)
TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0
JMP libc_munmap(SB) JMP libc_munmap(SB)
TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0
JMP libc___sysctl(SB)
TEXT ·libc_ptrace_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_ptrace_trampoline(SB),NOSPLIT,$0-0
JMP libc_ptrace(SB) JMP libc_ptrace(SB)
TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0

View file

@ -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 // 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) { 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)) _, _, e1 := Syscall6(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags))
if e1 != 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 // 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) { 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) _, _, e1 := Syscall6(SYS_PTRACE, uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
if e1 != 0 { if e1 != 0 {

View file

@ -757,6 +757,27 @@ func libc_ioctl_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT // 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) { 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)) _, _, e1 := syscall_syscall6(funcPC(libc_sendfile_trampoline), uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags))
if e1 != 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 // 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) { 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) _, _, e1 := syscall_syscall6(funcPC(libc_ptrace_trampoline), uintptr(request), uintptr(pid), uintptr(addr), uintptr(data), 0, 0)
if e1 != 0 { if e1 != 0 {

View file

@ -40,6 +40,8 @@ TEXT ·libc_sendmsg_trampoline(SB),NOSPLIT,$0-0
JMP libc_sendmsg(SB) JMP libc_sendmsg(SB)
TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0
JMP libc_kevent(SB) JMP libc_kevent(SB)
TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0
JMP libc___sysctl(SB)
TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0
JMP libc_utimes(SB) JMP libc_utimes(SB)
TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0
@ -262,8 +264,6 @@ TEXT ·libc_mmap_trampoline(SB),NOSPLIT,$0-0
JMP libc_mmap(SB) JMP libc_mmap(SB)
TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_munmap_trampoline(SB),NOSPLIT,$0-0
JMP libc_munmap(SB) JMP libc_munmap(SB)
TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0
JMP libc___sysctl(SB)
TEXT ·libc_ptrace_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_ptrace_trampoline(SB),NOSPLIT,$0-0
JMP libc_ptrace(SB) JMP libc_ptrace(SB)
TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_gettimeofday_trampoline(SB),NOSPLIT,$0-0

View file

@ -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 // 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) { 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) _, _, 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 { if e1 != 0 {

View file

@ -757,6 +757,27 @@ func libc_ioctl_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT // 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) { 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) _, _, 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 { if e1 != 0 {

View file

@ -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 // 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) { 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)) _, _, e1 := Syscall6(SYS_SENDFILE, uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags))
if e1 != 0 { if e1 != 0 {

View file

@ -757,6 +757,27 @@ func libc_ioctl_trampoline()
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT // 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) { 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)) _, _, e1 := syscall_syscall6(funcPC(libc_sendfile_trampoline), uintptr(infd), uintptr(outfd), uintptr(offset), uintptr(unsafe.Pointer(len)), uintptr(hdtr), uintptr(flags))
if e1 != 0 { if e1 != 0 {

View file

@ -40,6 +40,8 @@ TEXT ·libc_sendmsg_trampoline(SB),NOSPLIT,$0-0
JMP libc_sendmsg(SB) JMP libc_sendmsg(SB)
TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_kevent_trampoline(SB),NOSPLIT,$0-0
JMP libc_kevent(SB) JMP libc_kevent(SB)
TEXT ·libc___sysctl_trampoline(SB),NOSPLIT,$0-0
JMP libc___sysctl(SB)
TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_utimes_trampoline(SB),NOSPLIT,$0-0
JMP libc_utimes(SB) JMP libc_utimes(SB)
TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_futimes_trampoline(SB),NOSPLIT,$0-0
@ -104,8 +106,6 @@ TEXT ·libc_chown_trampoline(SB),NOSPLIT,$0-0
JMP libc_chown(SB) JMP libc_chown(SB)
TEXT ·libc_chroot_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_chroot_trampoline(SB),NOSPLIT,$0-0
JMP libc_chroot(SB) 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 TEXT ·libc_close_trampoline(SB),NOSPLIT,$0-0
JMP libc_close(SB) JMP libc_close(SB)
TEXT ·libc_dup_trampoline(SB),NOSPLIT,$0-0 TEXT ·libc_dup_trampoline(SB),NOSPLIT,$0-0

View file

@ -26,7 +26,10 @@ import (
const iexportVersion = 0 const iexportVersion = 0
// IExportData returns the binary export data for pkg. // IExportData returns the binary export data for pkg.
//
// If no file set is provided, position info will be missing. // 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) { func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
defer func() { defer func() {
if e := recover(); e != nil { 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{}, stringIndex: map[string]uint64{},
declIndex: map[types.Object]uint64{}, declIndex: map[types.Object]uint64{},
typIndex: map[types.Type]uint64{}, typIndex: map[types.Type]uint64{},
localpkg: pkg,
} }
for i, pt := range predeclared() { 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. // Append indices to data0 section.
dataLen := uint64(p.data0.Len()) dataLen := uint64(p.data0.Len())
w := p.newWriter() w := p.newWriter()
w.writeIndex(p.declIndex, pkg) w.writeIndex(p.declIndex)
w.flush() w.flush()
// Assemble header. // 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 // we're writing out the main index, which is also read by
// non-compiler tools and includes a complete package description // non-compiler tools and includes a complete package description
// (i.e., name and height). // (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. // Build a map from packages to objects from that package.
pkgObjs := map[*types.Package][]types.Object{} pkgObjs := map[*types.Package][]types.Object{}
// For the main index, make sure to include every package that // For the main index, make sure to include every package that
// we reference, even if we're not exporting (or reexporting) // we reference, even if we're not exporting (or reexporting)
// any symbols from it. // any symbols from it.
pkgObjs[localpkg] = nil pkgObjs[w.p.localpkg] = nil
for pkg := range w.p.allPkgs { for pkg := range w.p.allPkgs {
pkgObjs[pkg] = nil 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 { 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))) w.uint64(uint64(len(pkgs)))
for _, pkg := range pkgs { for _, pkg := range pkgs {
w.string(pkg.Path()) w.string(w.exportPath(pkg))
w.string(pkg.Name()) w.string(pkg.Name())
w.uint64(uint64(0)) // package height is not needed for go/types w.uint64(uint64(0)) // package height is not needed for go/types
@ -141,6 +145,8 @@ type iexporter struct {
fset *token.FileSet fset *token.FileSet
out *bytes.Buffer out *bytes.Buffer
localpkg *types.Package
// allPkgs tracks all packages that have been referenced by // allPkgs tracks all packages that have been referenced by
// the export data, so we can ensure to include them in the // the export data, so we can ensure to include them in the
// main index. // main index.
@ -193,6 +199,13 @@ type exportWriter struct {
prevLine int64 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) { func (p *iexporter) doDecl(obj types.Object) {
w := p.newWriter() w := p.newWriter()
w.setPkg(obj.Pkg(), false) 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. // Ensure any referenced packages are declared in the main index.
w.p.allPkgs[pkg] = true w.p.allPkgs[pkg] = true
w.string(pkg.Path()) w.string(w.exportPath(pkg))
} }
func (w *exportWriter) qualifiedIdent(obj types.Object) { func (w *exportWriter) qualifiedIdent(obj types.Object) {

View file

@ -147,24 +147,14 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
errorf("no packages found for %s", path) errorf("no packages found for %s", path)
panic("unreachable") panic("unreachable")
} }
var localpkg *types.Package p.ipkg = pkgList[0]
for _, pkg := range pkgList { names := make([]string, 0, len(p.pkgIndex[p.ipkg]))
if pkg.Path() == path { for name := range p.pkgIndex[p.ipkg] {
localpkg = pkg
break
}
}
if localpkg == nil {
localpkg = pkgList[0]
}
names := make([]string, 0, len(p.pkgIndex[localpkg]))
for name := range p.pkgIndex[localpkg] {
names = append(names, name) names = append(names, name)
} }
sort.Strings(names) sort.Strings(names)
for _, name := range names { for _, name := range names {
p.doDecl(localpkg, name) p.doDecl(p.ipkg, name)
} }
for _, typ := range p.interfaceList { 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 // record all referenced packages as imports
list := append(([]*types.Package)(nil), pkgList[1:]...) list := append(([]*types.Package)(nil), pkgList[1:]...)
sort.Sort(byPath(list)) sort.Sort(byPath(list))
localpkg.SetImports(list) p.ipkg.SetImports(list)
// package was imported completely and without errors // package was imported completely and without errors
localpkg.MarkComplete() p.ipkg.MarkComplete()
consumed, _ := r.Seek(0, io.SeekCurrent) consumed, _ := r.Seek(0, io.SeekCurrent)
return int(consumed), localpkg, nil return int(consumed), p.ipkg, nil
} }
type iimporter struct { type iimporter struct {
ipath string ipath string
ipkg *types.Package
version int version int
stringData []byte stringData []byte
@ -236,6 +227,9 @@ func (p *iimporter) pkgAt(off uint64) *types.Package {
return pkg return pkg
} }
path := p.stringAt(off) path := p.stringAt(off)
if path == p.ipath {
return p.ipkg
}
errorf("missing package %q in %q", path, p.ipath) errorf("missing package %q in %q", path, p.ipath)
return nil return nil
} }

View file

@ -26,6 +26,7 @@ import (
"golang.org/x/tools/go/internal/packagesdriver" "golang.org/x/tools/go/internal/packagesdriver"
"golang.org/x/tools/internal/gopathwalk" "golang.org/x/tools/internal/gopathwalk"
"golang.org/x/tools/internal/semver" "golang.org/x/tools/internal/semver"
"golang.org/x/tools/internal/span"
) )
// debug controls verbose logging. // 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) return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
} }
dirResponse, err := driver(cfg, pattern) dirResponse, err := driver(cfg, pattern)
if err != nil || (len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1) { if err != nil {
// 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.
var queryErr error var queryErr error
dirResponse, queryErr = driver(cfg, query) if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil {
if queryErr != nil { return err // return the original error
// Return the original error if the attempt to fall back failed.
return err
}
// 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)) isRoot := make(map[string]bool, len(dirResponse.Roots))
@ -332,6 +345,74 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
return nil 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. // modCacheRegexp splits a path in a module cache into module, module version, and package.
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
@ -395,6 +476,10 @@ func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, quer
} }
files, err := ioutil.ReadDir(modRoot) files, err := ioutil.ReadDir(modRoot)
if err != nil {
panic(err) // See above.
}
for _, f := range files { for _, f := range files {
if strings.HasSuffix(f.Name(), ".go") { if strings.HasSuffix(f.Name(), ".go") {
simpleMatches = append(simpleMatches, rel) 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 // We're only trying to look at stuff in the module cache, so
// disable the network. This should speed things up, and has // disable the network. This should speed things up, and has
// prevented errors in at least one case, #28518. // 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 var err error
tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery") tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery")
@ -510,17 +595,29 @@ func roots(cfg *Config) ([]gopathwalk.Root, string, error) {
var roots []gopathwalk.Root var roots []gopathwalk.Root
// Always add GOROOT. // 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 modules are enabled, scan the module dir.
if modDir != "" { 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. // Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode.
for _, p := range gopath { for _, p := range gopath {
if modDir != "" { 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 { } 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 // contained in a known module or GOPATH entry. This will allow the package to be
// properly "reclaimed" when overlays are processed. // properly "reclaimed" when overlays are processed.
if filepath.IsAbs(p.ImportPath) && p.Error != nil { if filepath.IsAbs(p.ImportPath) && p.Error != nil {
pkgPath, ok := getPkgPath(p.ImportPath, rootsDirs) pkgPath, ok := getPkgPath(cfg, p.ImportPath, rootsDirs)
if ok { if ok {
p.ImportPath = pkgPath 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. // 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 { 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. // TODO(matloob): This doesn't properly handle symlinks.
r, err := filepath.Rel(rdir, dir) r, err := filepath.Rel(rdir, dir)
if err != nil { if err != nil {
continue continue
} }
if rpath != "" { 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 // 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. // 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 // 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? // TODO(matloob): Implement module tiebreaking?
return path.Join(rpath, filepath.ToSlash(r)), true return path.Join(rpath, filepath.ToSlash(r)), true
} }
return filepath.ToSlash(r), true
} }
return "", false return "", false
} }
@ -859,7 +973,7 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) {
cmd.Stdout = stdout cmd.Stdout = stdout
cmd.Stderr = stderr cmd.Stderr = stderr
defer func(start time.Time) { 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()) }(time.Now())
if err := cmd.Run(); err != nil { 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 // (the Graphic characters without spaces) and may also exclude the
// characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD.
return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && 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 len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") {
if strings.HasPrefix(strings.TrimLeftFunc(stderr.String()[len("# "):], isPkgPathRune), "\n") { 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 // 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. // a zero exit status and set an error on that package.
if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { 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 // try to extract package name from string
stderrStr := stderr.String() stderrStr := stderr.String()
var importPath 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") != "" { if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" {
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr) 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 return stdout, nil
} }

View file

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"go/parser" "go/parser"
"go/token" "go/token"
"path"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -87,26 +86,10 @@ func processGolistOverlay(cfg *Config, response *responseDeduper, rootDirs func(
if pkg == nil { if pkg == nil {
// Try to find the module or gopath dir the file is contained in. // Try to find the module or gopath dir the file is contained in.
// Then for modules, add the module opath to the beginning. // Then for modules, add the module opath to the beginning.
var pkgPath string pkgPath, ok := getPkgPath(cfg, dir, rootDirs)
for rdir, rpath := range rootDirs().rootDirs { if !ok {
// 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?
break break
} }
if pkgPath == "" {
continue
}
isXTest := strings.HasSuffix(pkgName, "_test") isXTest := strings.HasSuffix(pkgName, "_test")
if isXTest { if isXTest {
pkgPath += "_test" pkgPath += "_test"

View file

@ -16,6 +16,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"golang.org/x/tools/internal/fastwalk" "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 return
} }
start := time.Now()
if opts.Debug { if opts.Debug {
log.Printf("scanning %s", root.Path) log.Printf("gopathwalk: scanning %s", root.Path)
} }
w := &walker{ w := &walker{
root: root, root: root,
@ -98,7 +100,7 @@ func walkDir(root Root, add func(Root, string), skip func(root Root, dir string)
} }
if opts.Debug { if opts.Debug {
log.Printf("scanned %s", root.Path) log.Printf("gopathwalk: scanned %s in %v", root.Path, time.Since(start))
} }
} }

100
vendor/golang.org/x/tools/internal/span/parse.go generated vendored Normal file
View file

@ -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}
}

285
vendor/golang.org/x/tools/internal/span/span.go generated vendored Normal file
View file

@ -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
}

151
vendor/golang.org/x/tools/internal/span/token.go generated vendored Normal file
View file

@ -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)
}

39
vendor/golang.org/x/tools/internal/span/token111.go generated vendored Normal file
View file

@ -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
}
}
}

16
vendor/golang.org/x/tools/internal/span/token112.go generated vendored Normal file
View file

@ -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)
}

152
vendor/golang.org/x/tools/internal/span/uri.go generated vendored Normal file
View file

@ -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] == ':'
}

94
vendor/golang.org/x/tools/internal/span/utf16.go generated vendored Normal file
View file

@ -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
}

17
vendor/modules.txt vendored
View file

@ -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
code.vikunja.io/web/handler code.vikunja.io/web/handler
# github.com/BurntSushi/toml v0.3.1 # 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 v0.0.0-20170703170152-9740945f5dcb
github.com/jgautheron/goconst/cmd/goconst github.com/jgautheron/goconst/cmd/goconst
github.com/jgautheron/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
github.com/labstack/echo/v4/middleware 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/log
github.com/labstack/gommon/color github.com/labstack/gommon/color
github.com/labstack/gommon/bytes github.com/labstack/gommon/bytes
@ -102,7 +102,7 @@ github.com/magiconair/properties
github.com/mailru/easyjson/jlexer github.com/mailru/easyjson/jlexer
github.com/mailru/easyjson/jwriter github.com/mailru/easyjson/jwriter
github.com/mailru/easyjson/buffer 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-colorable
# github.com/mattn/go-isatty v0.0.10 # github.com/mattn/go-isatty v0.0.10
github.com/mattn/go-isatty github.com/mattn/go-isatty
@ -187,7 +187,7 @@ github.com/ulule/limiter/v3/drivers/store/common
github.com/urfave/cli github.com/urfave/cli
# github.com/valyala/bytebufferpool v1.0.0 # github.com/valyala/bytebufferpool v1.0.0
github.com/valyala/bytebufferpool github.com/valyala/bytebufferpool
# github.com/valyala/fasttemplate v1.0.1 # github.com/valyala/fasttemplate v1.1.0
github.com/valyala/fasttemplate github.com/valyala/fasttemplate
# golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 # golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
golang.org/x/crypto/bcrypt 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 v0.0.0-20190409202823-959b441ac422
golang.org/x/lint/golint golang.org/x/lint/golint
golang.org/x/lint 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/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/sys/unix
# golang.org/x/text v0.3.2 # golang.org/x/text v0.3.2
golang.org/x/text/transform 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/secure/bidirule
golang.org/x/text/unicode/bidi golang.org/x/text/unicode/bidi
golang.org/x/text/width 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/loader
golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/ast/astutil
golang.org/x/tools/go/gcexportdata 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/go/internal/packagesdriver
golang.org/x/tools/internal/gopathwalk golang.org/x/tools/internal/gopathwalk
golang.org/x/tools/internal/semver golang.org/x/tools/internal/semver
golang.org/x/tools/internal/span
golang.org/x/tools/internal/fastwalk golang.org/x/tools/internal/fastwalk
# google.golang.org/appengine v1.5.0 # google.golang.org/appengine v1.5.0
google.golang.org/appengine/cloudsql google.golang.org/appengine/cloudsql