Sync installation messages & contact requests (#1791)

This commit is contained in:
Andrea Maria Piana 2020-01-15 08:25:09 +01:00 committed by GitHub
parent eb93bab35d
commit c569d8a4ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1802 additions and 457 deletions

9
go.mod
View File

@ -8,7 +8,7 @@ replace github.com/Sirupsen/logrus v1.4.2 => github.com/sirupsen/logrus v1.4.2
replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20190717161051-705d9623b7c1
replace github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
replace github.com/gomarkdown/markdown v0.0.0-20191209105822-e3ba6c6109ba => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
replace github.com/status-im/status-go/protocol => ./protocol
@ -23,7 +23,9 @@ replace github.com/status-im/status-go/waku => ./waku
require (
github.com/beevik/ntp v0.2.0
github.com/ethereum/go-ethereum v1.9.5
github.com/go-playground/universal-translator v0.17.0 // indirect
github.com/golang/mock v1.3.1
github.com/leodido/go-urn v1.2.0 // indirect
github.com/lib/pq v1.2.0
github.com/libp2p/go-libp2p v0.4.2 // indirect
github.com/libp2p/go-libp2p-core v0.2.4
@ -33,6 +35,8 @@ require (
github.com/pborman/uuid v1.2.0
github.com/pkg/errors v0.8.1
github.com/prometheus/client_golang v1.2.1
github.com/russolsen/ohyeah v0.0.0-20160324131710-f4938c005315 // indirect
github.com/russolsen/same v0.0.0-20160222130632-f089df61f51d // indirect
github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a
github.com/status-im/migrate/v4 v4.6.2-status.2
github.com/status-im/rendezvous v1.3.0
@ -46,6 +50,7 @@ require (
github.com/syndtr/goleveldb v1.0.0
go.uber.org/zap v1.13.0
golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c
gopkg.in/go-playground/validator.v9 v9.29.1
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)

15
go.sum
View File

@ -343,7 +343,6 @@ github.com/libp2p/go-flow-metrics v0.0.1 h1:0gxuFd2GuK7IIP5pKljLwps6TvcuYgvG7Atq
github.com/libp2p/go-flow-metrics v0.0.1/go.mod h1:Iv1GH0sG8DtYN3SVJ2eG221wMiNpZxBdp967ls1g+k8=
github.com/libp2p/go-libp2p v0.1.1 h1:52sB0TJuDk2nYMcMfHOKaPoaayDZjaYVCq6Vk1ejUTk=
github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8=
github.com/libp2p/go-libp2p v0.4.0/go.mod h1:9EsEIf9p2UDuwtPd0DwJsAl0qXVxgAnuDGRvHbfATfI=
github.com/libp2p/go-libp2p v0.4.2 h1:p0cthB0jDNHO4gH2HzS8/nAMMXbfUlFHs0jwZ4U+F2g=
github.com/libp2p/go-libp2p v0.4.2/go.mod h1:MNmgUxUw5pMsdOzMlT0EE7oKjRasl+WyVwM0IBlpKgQ=
github.com/libp2p/go-libp2p-autonat v0.1.0 h1:aCWAu43Ri4nU0ZPO7NyLzUvvfqd0nE3dX0R/ZGYVgOU=
@ -356,7 +355,6 @@ github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUA
github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU=
github.com/libp2p/go-libp2p-circuit v0.1.0 h1:eniLL3Y9aq/sryfyV1IAHj5rlvuyj3b7iz8tSiZpdhY=
github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8=
github.com/libp2p/go-libp2p-circuit v0.1.3/go.mod h1:Xqh2TjSy8DD5iV2cCOMzdynd6h8OTBGoV1AWbWor3qM=
github.com/libp2p/go-libp2p-circuit v0.1.4 h1:Phzbmrg3BkVzbqd4ZZ149JxCuUWu2wZcXf/Kr6hZJj8=
github.com/libp2p/go-libp2p-circuit v0.1.4/go.mod h1:CY67BrEjKNDhdTk8UgBX1Y/H5c3xkAcs3gnksxY7osU=
github.com/libp2p/go-libp2p-core v0.0.1/go.mod h1:g/VxnTZ/1ygHxH3dKok7Vno1VfpvGcGip57wjTU4fco=
@ -364,7 +362,6 @@ github.com/libp2p/go-libp2p-core v0.0.3/go.mod h1:j+YQMNz9WNSkNezXOsahp9kwZBKBvx
github.com/libp2p/go-libp2p-core v0.0.4/go.mod h1:jyuCQP356gzfCFtRKyvAbNkyeuxb7OlyhWZ3nls5d2I=
github.com/libp2p/go-libp2p-core v0.2.0/go.mod h1:X0eyB0Gy93v0DZtSYbEM7RnMChm9Uv3j7yRXjO77xSI=
github.com/libp2p/go-libp2p-core v0.2.2/go.mod h1:8fcwTbsG2B+lTgRJ1ICZtiM5GWCWZVoVrLaDRvIRng0=
github.com/libp2p/go-libp2p-core v0.2.3/go.mod h1:GqhyQqyIAPsxFYXHMjfXgMv03lxsvM0mFzuYA9Ib42A=
github.com/libp2p/go-libp2p-core v0.2.4 h1:Et6ykkTwI6PU44tr8qUF9k43vP0aduMNniShAbUJJw8=
github.com/libp2p/go-libp2p-core v0.2.4/go.mod h1:STh4fdfa5vDYr0/SzYYeqnt+E6KfEV5VxfIrm0bcI0g=
github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ=
@ -504,7 +501,6 @@ github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU
github.com/multiformats/go-multiaddr-dns v0.0.1/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
github.com/multiformats/go-multiaddr-dns v0.0.2 h1:/Bbsgsy3R6e3jf2qBahzNHzww6usYaZ0NhNH3sqdFS8=
github.com/multiformats/go-multiaddr-dns v0.0.2/go.mod h1:9kWcqw/Pj6FwxAwW38n/9403szc57zJPs45fmnznu3Q=
github.com/multiformats/go-multiaddr-dns v0.1.0/go.mod h1:01k2RAqtoXIuPa3DCavAE9/6jc6nM0H3EgZyfUhN2oY=
github.com/multiformats/go-multiaddr-dns v0.2.0 h1:YWJoIDwLePniH7OU5hBnDZV6SWuvJqJ0YtN6pLeH9zA=
github.com/multiformats/go-multiaddr-dns v0.2.0/go.mod h1:TJ5pr5bBO7Y1B18djPuRsVkduhQH2YqYSbxWJzYGdK0=
github.com/multiformats/go-multiaddr-fmt v0.0.1 h1:5YjeOIzbX8OTKVaN72aOzGIYW7PnrZrnkDyOfAWRSMA=
@ -659,7 +655,6 @@ github.com/status-im/migrate/v4 v4.6.2-status.2 h1:SdC+sMDl/aI7vUlwD2qj2p7KsK4T6
github.com/status-im/migrate/v4 v4.6.2-status.2/go.mod h1:c/kc90n47GZu/58nnz1OMLTf7uE4Da4gZP5qmU+A/v8=
github.com/status-im/rendezvous v1.3.0 h1:7RK/MXXW+tlm0asKm1u7Qp7Yni6AO29a7j8+E4Lbjg4=
github.com/status-im/rendezvous v1.3.0/go.mod h1:+hzjuP+j/XzLPeF6E50b88pWOTLdTcwjvNYt+Gh1W1s=
github.com/status-im/status-go v0.38.5/go.mod h1:UKxySGdqFuVPvCyPSYD0+zKeV2OzDijZqICvR+tZ3uM=
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501 h1:oa0KU5jJRNtXaM/P465MhvSFo/HM2O8qi2DDuPcd7ro=
github.com/status-im/tcp-shaker v0.0.0-20191114194237-215893130501/go.mod h1:RYo/itke1oU5k/6sj9DNM3QAwtE5rZSgg5JnkOv83hk=
github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE=
@ -754,7 +749,6 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE=
golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -787,9 +781,8 @@ golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2 h1:4dVFTC832rPn4pomLSz1vA+are2+dU19w1H8OngV7nc=
golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA=
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -851,8 +844,6 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101 h1:LCmXVkvpQCDj724eX6irUTPCJP5GelFHxqGSWL2D1R0=
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191213032237-7093a17b0467 h1:Jybbe55FT+YYZIJGWmJIA4ZGcglFuZOduakIW3+gHXY=
golang.org/x/tools v0.0.0-20191213032237-7093a17b0467/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
@ -885,8 +876,8 @@ gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1 h1:SvGtYmN60a5CVKTOzMSyfzWDeZRxRuGvRQyEAKbw1xc=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M=
gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=

View File

@ -75,6 +75,10 @@ func (c *Chat) PublicKey() (*ecdsa.PublicKey, error) {
}
func (c *Chat) Public() bool {
return c.ChatType == ChatTypePublic
}
func (c *Chat) MarshalJSON() ([]byte, error) {
type ChatAlias Chat
item := struct {
@ -212,20 +216,22 @@ func OneToOneFromPublicKey(pk *ecdsa.PublicKey) *Chat {
func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey) Chat {
return Chat{
ID: oneToOneChatID(publicKey),
Name: name,
Active: true,
ChatType: ChatTypeOneToOne,
ID: oneToOneChatID(publicKey),
Name: name,
Timestamp: int64(timestampInMs()),
Active: true,
ChatType: ChatTypeOneToOne,
}
}
func CreatePublicChat(name string) Chat {
return Chat{
ID: name,
Name: name,
Active: true,
Color: chatColors[rand.Intn(len(chatColors))],
ChatType: ChatTypePublic,
ID: name,
Name: name,
Active: true,
Timestamp: int64(timestampInMs()),
Color: chatColors[rand.Intn(len(chatColors))],
ChatType: ChatTypePublic,
}
}

View File

@ -106,3 +106,8 @@ func buildContact(publicKey *ecdsa.PublicKey) (*Contact, error) {
return contact, nil
}
func contactIDFromPublicKey(key *ecdsa.PublicKey) string {
return types.EncodeHex(crypto.FromECDSAPub(key))
}

View File

@ -4,7 +4,7 @@ go 1.13
replace github.com/ethereum/go-ethereum v1.9.5 => github.com/status-im/go-ethereum v1.9.5-status.7
replace github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
replace github.com/gomarkdown/markdown v0.0.0-20191209105822-e3ba6c6109ba => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
replace github.com/status-im/status-go/eth-node => ../eth-node
@ -14,7 +14,7 @@ require (
github.com/cenkalti/backoff/v3 v3.0.0
github.com/ethereum/go-ethereum v1.9.5
github.com/golang/protobuf v1.3.2
github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a
github.com/gomarkdown/markdown v0.0.0-20191209105822-e3ba6c6109ba
github.com/google/uuid v1.1.1
github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8
github.com/lucasb-eyer/go-colorful v1.0.2
@ -23,9 +23,8 @@ require (
github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a
github.com/status-im/doubleratchet v3.0.0+incompatible
github.com/status-im/migrate/v4 v4.6.2-status.2
github.com/status-im/status-go v0.38.5 // indirect
github.com/status-im/status-go/eth-node v1.0.0
github.com/status-im/status-go/whisper/v6 v6.0.1
github.com/status-im/status-go/eth-node v1.1.0
github.com/status-im/status-go/whisper/v6 v6.1.0
github.com/stretchr/testify v1.4.0
github.com/vacp2p/mvds v0.0.23
go.uber.org/zap v1.13.0

View File

@ -156,9 +156,11 @@ github.com/gizak/termui v0.0.0-20170117222342-991cd3d38091/go.mod h1:PkJoWUt/zac
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
@ -196,6 +198,7 @@ github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a h1:DsPLKbIJTzH
github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@ -289,6 +292,7 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
@ -763,6 +767,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101 h1:LCmXVkvpQCDj724eX6irUTPCJP5GelFHxqGSWL2D1R0=
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191213032237-7093a17b0467 h1:Jybbe55FT+YYZIJGWmJIA4ZGcglFuZOduakIW3+gHXY=
golang.org/x/tools v0.0.0-20191213032237-7093a17b0467/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=

View File

@ -113,6 +113,10 @@ type RawMessage struct {
func (m *Message) MarshalJSON() ([]byte, error) {
type MessageAlias Message
type StickerAlias struct {
Hash string `json:"hash"`
Pack int32 `json:"pack"`
}
item := struct {
ID string `json:"id"`
WhisperTimestamp uint64 `json:"whisperTimestamp"`
@ -132,7 +136,7 @@ func (m *Message) MarshalJSON() ([]byte, error) {
Replace string `json:"replace,omitEmpty"`
ResponseTo string `json:"responseTo"`
EnsName string `json:"ensName"`
Sticker *protobuf.StickerMessage `json:"sticker"`
Sticker *StickerAlias `json:"sticker"`
CommandParameters *CommandParameters `json:"commandParameters"`
Timestamp uint64 `json:"timestamp"`
ContentType protobuf.ChatMessage_ContentType `json:"contentType"`
@ -159,10 +163,15 @@ func (m *Message) MarshalJSON() ([]byte, error) {
Timestamp: m.Timestamp,
ContentType: m.ContentType,
MessageType: m.MessageType,
Sticker: m.GetSticker(),
CommandParameters: m.CommandParameters,
}
if sticker := m.GetSticker(); sticker != nil {
item.Sticker = &StickerAlias{
Pack: sticker.Pack,
Hash: sticker.Hash,
}
}
return json.Marshal(item)
}

View File

@ -7,7 +7,6 @@ import (
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf"
v1protocol "github.com/status-im/status-go/protocol/v1"
@ -49,7 +48,7 @@ func (m *MessageHandler) HandleMembershipUpdate(messageState *ReceivedMessageSta
}
// A new chat must contain us
if !group.IsMember(types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))) {
if !group.IsMember(contactIDFromPublicKey(&m.identity.PublicKey)) {
return errors.New("can't create a new group chat without us being a member")
}
newChat := createGroupChat()
@ -198,6 +197,21 @@ func (m *MessageHandler) HandleSyncInstallationContact(state *ReceivedMessageSta
return nil
}
func (m *MessageHandler) HandleSyncInstallationPublicChat(state *ReceivedMessageState, message protobuf.SyncInstallationPublicChat) error {
chatID := message.Id
_, ok := state.AllChats[chatID]
if ok {
return nil
}
chat := CreatePublicChat(chatID)
state.AllChats[chat.ID] = &chat
state.ModifiedChats[chat.ID] = true
return nil
}
func (m *MessageHandler) HandleContactUpdate(state *ReceivedMessageState, message protobuf.ContactUpdate) error {
logger := m.logger.With(zap.String("site", "HandleContactUpdate"))
contact := state.CurrentMessageState.Contact
@ -212,7 +226,7 @@ func (m *MessageHandler) HandleContactUpdate(state *ReceivedMessageState, messag
if contact.LastUpdated < message.Clock {
logger.Info("Updating contact")
if !contact.HasBeenAdded() {
if !contact.HasBeenAdded() && contact.ID != contactIDFromPublicKey(&m.identity.PublicKey) {
contact.SystemTags = append(contact.SystemTags, contactRequestReceived)
}
if contact.Name != message.EnsName {
@ -333,7 +347,7 @@ func (m *MessageHandler) HandleRequestAddressForTransaction(messageState *Receiv
Clock: command.Clock,
Timestamp: messageState.CurrentMessageState.WhisperTimestamp,
Text: "Request address for transaction",
ChatId: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
ChatId: contactIDFromPublicKey(&m.identity.PublicKey),
MessageType: protobuf.ChatMessage_ONE_TO_ONE,
ContentType: protobuf.ChatMessage_TRANSACTION_COMMAND,
},
@ -357,7 +371,7 @@ func (m *MessageHandler) HandleRequestTransaction(messageState *ReceivedMessageS
Clock: command.Clock,
Timestamp: messageState.CurrentMessageState.WhisperTimestamp,
Text: "Request transaction",
ChatId: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
ChatId: contactIDFromPublicKey(&m.identity.PublicKey),
MessageType: protobuf.ChatMessage_ONE_TO_ONE,
ContentType: protobuf.ChatMessage_TRANSACTION_COMMAND,
},
@ -563,7 +577,7 @@ func (m *MessageHandler) matchMessage(message *Message, chats map[string]*Chat)
case message.MessageType == protobuf.ChatMessage_ONE_TO_ONE:
// It's an incoming private message. ChatID is calculated from the signature.
// If a chat does not exist, a new one is created and saved.
chatID := types.EncodeHex(crypto.FromECDSAPub(message.SigPubKey))
chatID := contactIDFromPublicKey(message.SigPubKey)
chat := chats[chatID]
if chat == nil {
// TODO: this should be a three-word name used in the mobile client
@ -580,8 +594,8 @@ func (m *MessageHandler) matchMessage(message *Message, chats map[string]*Chat)
return nil, errors.New("received group chat message for non-existing chat")
}
theirKeyHex := types.EncodeHex(crypto.FromECDSAPub(message.SigPubKey))
myKeyHex := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
theirKeyHex := contactIDFromPublicKey(message.SigPubKey)
myKeyHex := contactIDFromPublicKey(&m.identity.PublicKey)
var theyJoined bool
var iJoined bool
for _, member := range chat.Members {

View File

@ -83,7 +83,7 @@ type MessengerResponse struct {
}
func (m *MessengerResponse) IsEmpty() bool {
return len(m.Chats) == 0 && len(m.Messages) == 0 && len(m.Contacts) == 0 && len(m.RawMessages) == 0
return len(m.Chats) == 0 && len(m.Messages) == 0 && len(m.Contacts) == 0 && len(m.RawMessages) == 0 && len(m.Installations) == 0
}
type featureFlags struct {
@ -216,7 +216,7 @@ func NewMessenger(
onNewInstallationsHandler := func(installations []*multidevice.Installation) {
for _, installation := range installations {
if installation.Identity == types.EncodeHex(crypto.FromECDSAPub(&messenger.identity.PublicKey)) {
if installation.Identity == contactIDFromPublicKey(&messenger.identity.PublicKey) {
if _, ok := messenger.allInstallations[installation.ID]; !ok {
messenger.allInstallations[installation.ID] = installation
messenger.modifiedInstallations[installation.ID] = true
@ -595,7 +595,7 @@ func (m *Messenger) Join(chat Chat) error {
}
return m.transport.JoinGroup(members)
case ChatTypePublic:
return m.transport.JoinPublic(chat.Name)
return m.transport.JoinPublic(chat.ID)
default:
return errors.New("chat is neither public nor private")
}
@ -638,8 +638,9 @@ func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string,
}
chat.updateChatFromProtocolGroup(group)
clock, _ := chat.NextClockAndTimestamp()
// Add members
event := v1protocol.NewMembersAddedEvent(members, group.NextClockValue())
event := v1protocol.NewMembersAddedEvent(members, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -704,8 +705,9 @@ func (m *Messenger) RemoveMemberFromGroupChat(ctx context.Context, chatID string
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
// Remove member
event := v1protocol.NewMemberRemovedEvent(member, group.NextClockValue())
event := v1protocol.NewMemberRemovedEvent(member, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -755,8 +757,9 @@ func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, me
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
// Add members
event := v1protocol.NewMembersAddedEvent(members, group.NextClockValue())
event := v1protocol.NewMembersAddedEvent(members, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -815,8 +818,9 @@ func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, mem
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
// Add members
event := v1protocol.NewAdminsAddedEvent(members, group.NextClockValue())
event := v1protocol.NewAdminsAddedEvent(members, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -877,8 +881,9 @@ func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*Me
if err != nil {
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
event := v1protocol.NewMemberJoinedEvent(
group.NextClockValue(),
clock,
)
event.ChatID = chat.ID
err = event.Sign(m.identity)
@ -939,9 +944,10 @@ func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string) (*Messeng
if err != nil {
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
event := v1protocol.NewMemberRemovedEvent(
types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
group.NextClockValue(),
contactIDFromPublicKey(&m.identity.PublicKey),
clock,
)
event.ChatID = chat.ID
err = event.Sign(m.identity)
@ -983,6 +989,14 @@ func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string) (*Messeng
}
func (m *Messenger) saveChat(chat *Chat) error {
_, ok := m.allChats[chat.ID]
// Sync chat if it's a new active public chat
if !ok && chat.Active && chat.Public() {
if err := m.syncPublicChat(context.Background(), chat); err != nil {
return err
}
}
err := m.persistence.SaveChat(*chat)
if err != nil {
return err
@ -1008,12 +1022,7 @@ func (m *Messenger) saveChats(chats []*Chat) error {
func (m *Messenger) SaveChat(chat *Chat) error {
m.mutex.Lock()
defer m.mutex.Unlock()
err := m.saveChat(chat)
if err != nil {
return err
}
return nil
return m.saveChat(chat)
}
func (m *Messenger) Chats() []*Chat {
@ -1345,6 +1354,13 @@ func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImag
m.mutex.Lock()
defer m.mutex.Unlock()
myID := contactIDFromPublicKey(&m.identity.PublicKey)
if _, err := m.sendContactUpdate(ctx, myID, ensName, profileImage); err != nil {
return err
}
// TODO: This should not be sending paired messages, as we do it above
for _, contact := range m.allContacts {
if contact.IsAdded() {
if _, err := m.sendContactUpdate(ctx, contact.ID, ensName, profileImage); err != nil {
@ -1353,7 +1369,6 @@ func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImag
}
}
return nil
}
// SendContactUpdate sends a contact update to a user and adds the user to contacts
@ -1417,7 +1432,7 @@ func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, prof
return nil, err
}
if !contact.IsAdded() {
if !contact.IsAdded() && contact.ID != contactIDFromPublicKey(&m.identity.PublicKey) {
contact.SystemTags = append(contact.SystemTags, contactAdded)
}
@ -1432,6 +1447,36 @@ func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, prof
return &response, m.saveContact(contact)
}
// SyncDevices sends all public chats and contacts to paired devices
func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
myID := contactIDFromPublicKey(&m.identity.PublicKey)
if _, err := m.sendContactUpdate(ctx, myID, ensName, photoPath); err != nil {
return err
}
for _, chat := range m.allChats {
if chat.Public() && chat.Active {
if err := m.syncPublicChat(ctx, chat); err != nil {
return err
}
}
}
for _, contact := range m.allContacts {
if contact.IsAdded() && contact.ID != myID {
if err := m.syncContact(ctx, contact); err != nil {
return err
}
}
}
return nil
}
// SendPairInstallation sends a pair installation message
func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerResponse, error) {
m.mutex.Lock()
@ -1449,7 +1494,7 @@ func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerRespons
return nil, errors.New("no installation metadata")
}
chatID := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
chat, ok := m.allChats[chatID]
if !ok {
@ -1491,13 +1536,54 @@ func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerRespons
return &response, nil
}
// syncPublicChat sync a public chat with paired devices
func (m *Messenger) syncPublicChat(ctx context.Context, publicChat *Chat) error {
var err error
if !m.hasPairedDevices() {
return nil
}
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
chat, ok := m.allChats[chatID]
if !ok {
chat = OneToOneFromPublicKey(&m.identity.PublicKey)
// We don't want to show the chat to the user
chat.Active = false
}
m.allChats[chat.ID] = chat
clock, _ := chat.NextClockAndTimestamp()
syncMessage := &protobuf.SyncInstallationPublicChat{
Clock: clock,
Id: publicChat.ID,
}
encodedMessage, err := proto.Marshal(syncMessage)
if err != nil {
return err
}
_, err = m.dispatchMessage(ctx, &RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_PUBLIC_CHAT,
ResendAutomatically: true,
})
if err != nil {
return err
}
chat.LastClockValue = clock
return m.saveChat(chat)
}
// syncContact sync as contact with paired devices
func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
var err error
if !m.hasPairedDevices() {
return nil
}
chatID := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
chat, ok := m.allChats[chatID]
if !ok {
@ -1624,7 +1710,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
publicKey := msg.SigPubKey()
// Check for messages from blocked users
senderID := types.EncodeHex(crypto.FromECDSAPub(publicKey))
senderID := contactIDFromPublicKey(publicKey)
if _, ok := messageState.AllContacts[senderID]; ok && messageState.AllContacts[senderID].IsBlocked() {
continue
}
@ -1706,6 +1792,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
logger.Warn("failed to handle SyncInstallationContact", zap.Error(err))
continue
}
case protobuf.SyncInstallationPublicChat:
if !isPubKeyEqual(messageState.CurrentMessageState.PublicKey, &m.identity.PublicKey) {
logger.Warn("not coming from us, ignoring")
continue
}
p := msg.ParsedMessage.(protobuf.SyncInstallationPublicChat)
logger.Debug("Handling SyncInstallationPublicChat", zap.Any("message", p))
err = m.handler.HandleSyncInstallationPublicChat(messageState, p)
if err != nil {
logger.Warn("failed to handle SyncInstallationPublicChat", zap.Error(err))
continue
}
case protobuf.RequestAddressForTransaction:
command := msg.ParsedMessage.(protobuf.RequestAddressForTransaction)
logger.Debug("Handling RequestAddressForTransaction", zap.Any("message", command))
@ -2479,7 +2578,7 @@ func (m *Messenger) AcceptRequestTransaction(ctx context.Context, transactionHas
return &response, m.saveChat(chat)
}
func (m *Messenger) SendTransaction(ctx context.Context, chatID, transactionHash string, signature []byte) (*MessengerResponse, error) {
func (m *Messenger) SendTransaction(ctx context.Context, chatID, value, contract, transactionHash string, signature []byte) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
@ -2534,6 +2633,8 @@ func (m *Messenger) SendTransaction(ctx context.Context, chatID, transactionHash
message.ID = types.EncodeHex(newMessageID)
message.CommandParameters = &CommandParameters{
TransactionHash: transactionHash,
Value: value,
Contract: contract,
Signature: signature,
CommandState: CommandStateTransactionSent,
}
@ -2584,7 +2685,7 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types.
}
for _, validationResult := range responses {
var message *Message
chatID := types.EncodeHex(crypto.FromECDSAPub(validationResult.Transaction.From))
chatID := contactIDFromPublicKey(validationResult.Transaction.From)
chat, ok := m.allChats[chatID]
if !ok {
chat = OneToOneFromPublicKey(validationResult.Transaction.From)

View File

@ -141,7 +141,135 @@ func (s *MessengerInstallationSuite) TestReceiveInstallation() {
actualContact := response.Contacts[0]
s.Require().Equal(contact.ID, actualContact.ID)
s.Require().True(actualContact.IsAdded())
chat := CreatePublicChat("status")
err = s.m.SaveChat(&chat)
s.Require().NoError(err)
// Wait for the message to reach its destination
err = tt.RetryWithBackOff(func() error {
var err error
response, err = theirMessenger.RetrieveAll()
if err == nil && len(response.Chats) == 0 {
err = errors.New("sync chat not received")
}
return err
})
s.Require().NoError(err)
actualChat := response.Chats[0]
s.Require().Equal("status", actualChat.ID)
s.Require().True(actualChat.Active)
}
func (s *MessengerInstallationSuite) TestReceiveSyncInstallation() {
func (s *MessengerInstallationSuite) TestSyncInstallation() {
// add contact
contactKey, err := crypto.GenerateKey()
s.Require().NoError(err)
contact, err := buildContact(&contactKey.PublicKey)
s.Require().NoError(err)
contact.SystemTags = append(contact.SystemTags, contactAdded)
err = s.m.SaveContact(contact)
s.Require().NoError(err)
// add chat
chat := CreatePublicChat("status")
err = s.m.SaveChat(&chat)
s.Require().NoError(err)
// pair
theirMessenger := s.newMessengerWithKey(s.shh, s.privateKey)
err = theirMessenger.SetInstallationMetadata(theirMessenger.installationID, &multidevice.InstallationMetadata{
Name: "their-name",
DeviceType: "their-device-type",
})
s.Require().NoError(err)
response, err := theirMessenger.SendPairInstallation(context.Background())
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Chats, 1)
s.Require().False(response.Chats[0].Active)
// Wait for the message to reach its destination
err = tt.RetryWithBackOff(func() error {
var err error
response, err = s.m.RetrieveAll()
if err == nil && len(response.Installations) == 0 {
err = errors.New("installation not received")
}
return err
})
s.Require().NoError(err)
actualInstallation := response.Installations[0]
s.Require().Equal(theirMessenger.installationID, actualInstallation.ID)
s.Require().NotNil(actualInstallation.InstallationMetadata)
s.Require().Equal("their-name", actualInstallation.InstallationMetadata.Name)
s.Require().Equal("their-device-type", actualInstallation.InstallationMetadata.DeviceType)
err = s.m.EnableInstallation(theirMessenger.installationID)
s.Require().NoError(err)
// sync
err = s.m.SyncDevices(context.Background(), "ens-name", "profile-image")
s.Require().NoError(err)
var allChats []*Chat
var allContacts []*Contact
// Wait for the message to reach its destination
err = tt.RetryWithBackOff(func() error {
var err error
response, err = theirMessenger.RetrieveAll()
if err != nil {
return err
}
allChats = append(allChats, response.Chats...)
allContacts = append(allContacts, response.Contacts...)
if len(allChats) >= 2 && len(allContacts) >= 3 {
return nil
}
return errors.New("Not received all chats & contacts")
})
s.Require().NoError(err)
var statusChat *Chat
for _, c := range allChats {
if c.ID == "status" {
statusChat = c
}
}
s.Require().NotNil(statusChat)
var actualContact *Contact
for _, c := range allContacts {
if c.ID == contact.ID {
actualContact = c
}
}
s.Require().True(actualContact.IsAdded())
var ourContact *Contact
myID := types.EncodeHex(crypto.FromECDSAPub(&s.m.identity.PublicKey))
for _, c := range allContacts {
if c.ID == myID {
if ourContact == nil || ourContact.LastUpdated < c.LastUpdated {
ourContact = c
}
}
}
s.Require().NotNil(ourContact)
s.Require().Equal("ens-name", ourContact.Name)
s.Require().Equal("profile-image", ourContact.Photo)
}

View File

@ -1014,6 +1014,19 @@ func (s *MessengerSuite) TestChatPersistenceOneToOne() {
}
func (s *MessengerSuite) TestChatPersistencePrivateGroupChat() {
member1Key, err := crypto.GenerateKey()
s.Require().NoError(err)
member1ID := types.EncodeHex(crypto.FromECDSAPub(&member1Key.PublicKey))
member2Key, err := crypto.GenerateKey()
s.Require().NoError(err)
member2ID := types.EncodeHex(crypto.FromECDSAPub(&member2Key.PublicKey))
member3Key, err := crypto.GenerateKey()
s.Require().NoError(err)
member3ID := types.EncodeHex(crypto.FromECDSAPub(&member3Key.PublicKey))
chat := Chat{
ID: "chat-id",
Name: "chat-id",
@ -1023,17 +1036,17 @@ func (s *MessengerSuite) TestChatPersistencePrivateGroupChat() {
Timestamp: 10,
Members: []ChatMember{
{
ID: "1",
ID: member1ID,
Admin: false,
Joined: true,
},
{
ID: "2",
ID: member2ID,
Admin: true,
Joined: false,
},
{
ID: "3",
ID: member3ID,
Admin: true,
Joined: true,
},
@ -1554,6 +1567,7 @@ func (s *MessengerSuite) TestDeclineRequestAddressForTransaction() {
func (s *MessengerSuite) TestSendEthTransaction() {
value := "2000"
contract := "some-contract"
theirMessenger := s.newMessenger(s.shh)
theirPkString := types.EncodeHex(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))
@ -1569,7 +1583,7 @@ func (s *MessengerSuite) TestSendEthTransaction() {
signature, err := buildSignature(s.m.identity, &s.m.identity.PublicKey, transactionHash)
s.Require().NoError(err)
response, err := s.m.SendTransaction(context.Background(), theirPkString, transactionHash, signature)
response, err := s.m.SendTransaction(context.Background(), theirPkString, value, contract, transactionHash, signature)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Chats, 1)
@ -1580,6 +1594,8 @@ func (s *MessengerSuite) TestSendEthTransaction() {
s.Require().Equal("Transaction sent", senderMessage.Text)
s.Require().NotNil(senderMessage.CommandParameters)
s.Require().Equal(transactionHash, senderMessage.CommandParameters.TransactionHash)
s.Require().Equal(contract, senderMessage.CommandParameters.Contract)
s.Require().Equal(value, senderMessage.CommandParameters.Value)
s.Require().Equal(signature, senderMessage.CommandParameters.Signature)
s.Require().Equal(CommandStateTransactionSent, senderMessage.CommandParameters.CommandState)
s.Require().NotEmpty(senderMessage.ID)
@ -1667,7 +1683,7 @@ func (s *MessengerSuite) TestSendTokenTransaction() {
signature, err := buildSignature(s.m.identity, &s.m.identity.PublicKey, transactionHash)
s.Require().NoError(err)
response, err := s.m.SendTransaction(context.Background(), theirPkString, transactionHash, signature)
response, err := s.m.SendTransaction(context.Background(), theirPkString, value, contract, transactionHash, signature)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Chats, 1)
@ -1678,6 +1694,8 @@ func (s *MessengerSuite) TestSendTokenTransaction() {
s.Require().Equal("Transaction sent", senderMessage.Text)
s.Require().NotNil(senderMessage.CommandParameters)
s.Require().Equal(transactionHash, senderMessage.CommandParameters.TransactionHash)
s.Require().Equal(value, senderMessage.CommandParameters.Value)
s.Require().Equal(contract, senderMessage.CommandParameters.Contract)
s.Require().Equal(signature, senderMessage.CommandParameters.Signature)
s.Require().Equal(CommandStateTransactionSent, senderMessage.CommandParameters.CommandState)
s.Require().NotEmpty(senderMessage.ID)

View File

@ -342,10 +342,6 @@ func (g Group) LastClockValue() uint64 {
return g.events[len(g.events)-1].ClockValue
}
func (g Group) NextClockValue() uint64 {
return g.LastClockValue() + 1
}
func (g Group) creator() (string, error) {
if len(g.events) == 0 {
return "", errors.New("no events in the group")

View File

@ -721,8 +721,8 @@ func (api *PublicAPI) AcceptRequestAddressForTransaction(ctx context.Context, me
return api.service.messenger.AcceptRequestAddressForTransaction(ctx, messageID, address)
}
func (api *PublicAPI) SendTransaction(ctx context.Context, chatID, transactionHash string, signature types.HexBytes) (*protocol.MessengerResponse, error) {
return api.service.messenger.SendTransaction(ctx, chatID, transactionHash, signature)
func (api *PublicAPI) SendTransaction(ctx context.Context, chatID, value, contract, transactionHash string, signature types.HexBytes) (*protocol.MessengerResponse, error) {
return api.service.messenger.SendTransaction(ctx, chatID, value, contract, transactionHash, signature)
}
func (api *PublicAPI) AcceptRequestTransaction(ctx context.Context, transactionHash, messageID string, signature types.HexBytes) (*protocol.MessengerResponse, error) {
@ -737,6 +737,14 @@ func (api *PublicAPI) SendContactUpdate(ctx context.Context, contactID, name, pi
return api.service.messenger.SendContactUpdate(ctx, contactID, name, picture)
}
func (api *PublicAPI) SendPairInstallation(ctx context.Context) (*protocol.MessengerResponse, error) {
return api.service.messenger.SendPairInstallation(ctx)
}
func (api *PublicAPI) SyncDevices(ctx context.Context, name, picture string) error {
return api.service.messenger.SyncDevices(ctx, name, picture)
}
// -----
// HELPER
// -----

View File

@ -75,6 +75,10 @@ func (c *Chat) PublicKey() (*ecdsa.PublicKey, error) {
}
func (c *Chat) Public() bool {
return c.ChatType == ChatTypePublic
}
func (c *Chat) MarshalJSON() ([]byte, error) {
type ChatAlias Chat
item := struct {
@ -212,20 +216,22 @@ func OneToOneFromPublicKey(pk *ecdsa.PublicKey) *Chat {
func CreateOneToOneChat(name string, publicKey *ecdsa.PublicKey) Chat {
return Chat{
ID: oneToOneChatID(publicKey),
Name: name,
Active: true,
ChatType: ChatTypeOneToOne,
ID: oneToOneChatID(publicKey),
Name: name,
Timestamp: int64(timestampInMs()),
Active: true,
ChatType: ChatTypeOneToOne,
}
}
func CreatePublicChat(name string) Chat {
return Chat{
ID: name,
Name: name,
Active: true,
Color: chatColors[rand.Intn(len(chatColors))],
ChatType: ChatTypePublic,
ID: name,
Name: name,
Active: true,
Timestamp: int64(timestampInMs()),
Color: chatColors[rand.Intn(len(chatColors))],
ChatType: ChatTypePublic,
}
}

View File

@ -106,3 +106,8 @@ func buildContact(publicKey *ecdsa.PublicKey) (*Contact, error) {
return contact, nil
}
func contactIDFromPublicKey(key *ecdsa.PublicKey) string {
return types.EncodeHex(crypto.FromECDSAPub(key))
}

View File

@ -4,7 +4,7 @@ go 1.13
replace github.com/ethereum/go-ethereum v1.9.5 => github.com/status-im/go-ethereum v1.9.5-status.7
replace github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
replace github.com/gomarkdown/markdown v0.0.0-20191209105822-e3ba6c6109ba => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
replace github.com/status-im/status-go/eth-node => ../eth-node
@ -14,7 +14,7 @@ require (
github.com/cenkalti/backoff/v3 v3.0.0
github.com/ethereum/go-ethereum v1.9.5
github.com/golang/protobuf v1.3.2
github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a
github.com/gomarkdown/markdown v0.0.0-20191209105822-e3ba6c6109ba
github.com/google/uuid v1.1.1
github.com/jinzhu/copier v0.0.0-20190625015134-976e0346caa8
github.com/lucasb-eyer/go-colorful v1.0.2
@ -23,9 +23,8 @@ require (
github.com/russolsen/transit v0.0.0-20180705123435-0794b4c4505a
github.com/status-im/doubleratchet v3.0.0+incompatible
github.com/status-im/migrate/v4 v4.6.2-status.2
github.com/status-im/status-go v0.38.5 // indirect
github.com/status-im/status-go/eth-node v1.0.0
github.com/status-im/status-go/whisper/v6 v6.0.1
github.com/status-im/status-go/eth-node v1.1.0
github.com/status-im/status-go/whisper/v6 v6.1.0
github.com/stretchr/testify v1.4.0
github.com/vacp2p/mvds v0.0.23
go.uber.org/zap v1.13.0

View File

@ -156,9 +156,11 @@ github.com/gizak/termui v0.0.0-20170117222342-991cd3d38091/go.mod h1:PkJoWUt/zac
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
@ -196,6 +198,7 @@ github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a h1:DsPLKbIJTzH
github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
@ -289,6 +292,7 @@ github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/reedsolomon v1.9.2/go.mod h1:CwCi+NUr9pqSVktrkN+Ondf06rkhYZ/pcNv7fu+8Un4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
@ -763,6 +767,7 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101 h1:LCmXVkvpQCDj724eX6irUTPCJP5GelFHxqGSWL2D1R0=
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191213032237-7093a17b0467 h1:Jybbe55FT+YYZIJGWmJIA4ZGcglFuZOduakIW3+gHXY=
golang.org/x/tools v0.0.0-20191213032237-7093a17b0467/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=

View File

@ -113,6 +113,10 @@ type RawMessage struct {
func (m *Message) MarshalJSON() ([]byte, error) {
type MessageAlias Message
type StickerAlias struct {
Hash string `json:"hash"`
Pack int32 `json:"pack"`
}
item := struct {
ID string `json:"id"`
WhisperTimestamp uint64 `json:"whisperTimestamp"`
@ -132,7 +136,7 @@ func (m *Message) MarshalJSON() ([]byte, error) {
Replace string `json:"replace,omitEmpty"`
ResponseTo string `json:"responseTo"`
EnsName string `json:"ensName"`
Sticker *protobuf.StickerMessage `json:"sticker"`
Sticker *StickerAlias `json:"sticker"`
CommandParameters *CommandParameters `json:"commandParameters"`
Timestamp uint64 `json:"timestamp"`
ContentType protobuf.ChatMessage_ContentType `json:"contentType"`
@ -159,10 +163,15 @@ func (m *Message) MarshalJSON() ([]byte, error) {
Timestamp: m.Timestamp,
ContentType: m.ContentType,
MessageType: m.MessageType,
Sticker: m.GetSticker(),
CommandParameters: m.CommandParameters,
}
if sticker := m.GetSticker(); sticker != nil {
item.Sticker = &StickerAlias{
Pack: sticker.Pack,
Hash: sticker.Hash,
}
}
return json.Marshal(item)
}

View File

@ -7,7 +7,6 @@ import (
"go.uber.org/zap"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf"
v1protocol "github.com/status-im/status-go/protocol/v1"
@ -49,7 +48,7 @@ func (m *MessageHandler) HandleMembershipUpdate(messageState *ReceivedMessageSta
}
// A new chat must contain us
if !group.IsMember(types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))) {
if !group.IsMember(contactIDFromPublicKey(&m.identity.PublicKey)) {
return errors.New("can't create a new group chat without us being a member")
}
newChat := createGroupChat()
@ -198,6 +197,21 @@ func (m *MessageHandler) HandleSyncInstallationContact(state *ReceivedMessageSta
return nil
}
func (m *MessageHandler) HandleSyncInstallationPublicChat(state *ReceivedMessageState, message protobuf.SyncInstallationPublicChat) error {
chatID := message.Id
_, ok := state.AllChats[chatID]
if ok {
return nil
}
chat := CreatePublicChat(chatID)
state.AllChats[chat.ID] = &chat
state.ModifiedChats[chat.ID] = true
return nil
}
func (m *MessageHandler) HandleContactUpdate(state *ReceivedMessageState, message protobuf.ContactUpdate) error {
logger := m.logger.With(zap.String("site", "HandleContactUpdate"))
contact := state.CurrentMessageState.Contact
@ -212,7 +226,7 @@ func (m *MessageHandler) HandleContactUpdate(state *ReceivedMessageState, messag
if contact.LastUpdated < message.Clock {
logger.Info("Updating contact")
if !contact.HasBeenAdded() {
if !contact.HasBeenAdded() && contact.ID != contactIDFromPublicKey(&m.identity.PublicKey) {
contact.SystemTags = append(contact.SystemTags, contactRequestReceived)
}
if contact.Name != message.EnsName {
@ -333,7 +347,7 @@ func (m *MessageHandler) HandleRequestAddressForTransaction(messageState *Receiv
Clock: command.Clock,
Timestamp: messageState.CurrentMessageState.WhisperTimestamp,
Text: "Request address for transaction",
ChatId: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
ChatId: contactIDFromPublicKey(&m.identity.PublicKey),
MessageType: protobuf.ChatMessage_ONE_TO_ONE,
ContentType: protobuf.ChatMessage_TRANSACTION_COMMAND,
},
@ -357,7 +371,7 @@ func (m *MessageHandler) HandleRequestTransaction(messageState *ReceivedMessageS
Clock: command.Clock,
Timestamp: messageState.CurrentMessageState.WhisperTimestamp,
Text: "Request transaction",
ChatId: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
ChatId: contactIDFromPublicKey(&m.identity.PublicKey),
MessageType: protobuf.ChatMessage_ONE_TO_ONE,
ContentType: protobuf.ChatMessage_TRANSACTION_COMMAND,
},
@ -563,7 +577,7 @@ func (m *MessageHandler) matchMessage(message *Message, chats map[string]*Chat)
case message.MessageType == protobuf.ChatMessage_ONE_TO_ONE:
// It's an incoming private message. ChatID is calculated from the signature.
// If a chat does not exist, a new one is created and saved.
chatID := types.EncodeHex(crypto.FromECDSAPub(message.SigPubKey))
chatID := contactIDFromPublicKey(message.SigPubKey)
chat := chats[chatID]
if chat == nil {
// TODO: this should be a three-word name used in the mobile client
@ -580,8 +594,8 @@ func (m *MessageHandler) matchMessage(message *Message, chats map[string]*Chat)
return nil, errors.New("received group chat message for non-existing chat")
}
theirKeyHex := types.EncodeHex(crypto.FromECDSAPub(message.SigPubKey))
myKeyHex := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
theirKeyHex := contactIDFromPublicKey(message.SigPubKey)
myKeyHex := contactIDFromPublicKey(&m.identity.PublicKey)
var theyJoined bool
var iJoined bool
for _, member := range chat.Members {

View File

@ -83,7 +83,7 @@ type MessengerResponse struct {
}
func (m *MessengerResponse) IsEmpty() bool {
return len(m.Chats) == 0 && len(m.Messages) == 0 && len(m.Contacts) == 0 && len(m.RawMessages) == 0
return len(m.Chats) == 0 && len(m.Messages) == 0 && len(m.Contacts) == 0 && len(m.RawMessages) == 0 && len(m.Installations) == 0
}
type featureFlags struct {
@ -216,7 +216,7 @@ func NewMessenger(
onNewInstallationsHandler := func(installations []*multidevice.Installation) {
for _, installation := range installations {
if installation.Identity == types.EncodeHex(crypto.FromECDSAPub(&messenger.identity.PublicKey)) {
if installation.Identity == contactIDFromPublicKey(&messenger.identity.PublicKey) {
if _, ok := messenger.allInstallations[installation.ID]; !ok {
messenger.allInstallations[installation.ID] = installation
messenger.modifiedInstallations[installation.ID] = true
@ -595,7 +595,7 @@ func (m *Messenger) Join(chat Chat) error {
}
return m.transport.JoinGroup(members)
case ChatTypePublic:
return m.transport.JoinPublic(chat.Name)
return m.transport.JoinPublic(chat.ID)
default:
return errors.New("chat is neither public nor private")
}
@ -638,8 +638,9 @@ func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string,
}
chat.updateChatFromProtocolGroup(group)
clock, _ := chat.NextClockAndTimestamp()
// Add members
event := v1protocol.NewMembersAddedEvent(members, group.NextClockValue())
event := v1protocol.NewMembersAddedEvent(members, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -704,8 +705,9 @@ func (m *Messenger) RemoveMemberFromGroupChat(ctx context.Context, chatID string
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
// Remove member
event := v1protocol.NewMemberRemovedEvent(member, group.NextClockValue())
event := v1protocol.NewMemberRemovedEvent(member, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -755,8 +757,9 @@ func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, me
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
// Add members
event := v1protocol.NewMembersAddedEvent(members, group.NextClockValue())
event := v1protocol.NewMembersAddedEvent(members, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -815,8 +818,9 @@ func (m *Messenger) AddAdminsToGroupChat(ctx context.Context, chatID string, mem
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
// Add members
event := v1protocol.NewAdminsAddedEvent(members, group.NextClockValue())
event := v1protocol.NewAdminsAddedEvent(members, clock)
event.ChatID = chat.ID
err = event.Sign(m.identity)
if err != nil {
@ -877,8 +881,9 @@ func (m *Messenger) ConfirmJoiningGroup(ctx context.Context, chatID string) (*Me
if err != nil {
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
event := v1protocol.NewMemberJoinedEvent(
group.NextClockValue(),
clock,
)
event.ChatID = chat.ID
err = event.Sign(m.identity)
@ -939,9 +944,10 @@ func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string) (*Messeng
if err != nil {
return nil, err
}
clock, _ := chat.NextClockAndTimestamp()
event := v1protocol.NewMemberRemovedEvent(
types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
group.NextClockValue(),
contactIDFromPublicKey(&m.identity.PublicKey),
clock,
)
event.ChatID = chat.ID
err = event.Sign(m.identity)
@ -983,6 +989,14 @@ func (m *Messenger) LeaveGroupChat(ctx context.Context, chatID string) (*Messeng
}
func (m *Messenger) saveChat(chat *Chat) error {
_, ok := m.allChats[chat.ID]
// Sync chat if it's a new active public chat
if !ok && chat.Active && chat.Public() {
if err := m.syncPublicChat(context.Background(), chat); err != nil {
return err
}
}
err := m.persistence.SaveChat(*chat)
if err != nil {
return err
@ -1008,12 +1022,7 @@ func (m *Messenger) saveChats(chats []*Chat) error {
func (m *Messenger) SaveChat(chat *Chat) error {
m.mutex.Lock()
defer m.mutex.Unlock()
err := m.saveChat(chat)
if err != nil {
return err
}
return nil
return m.saveChat(chat)
}
func (m *Messenger) Chats() []*Chat {
@ -1345,6 +1354,13 @@ func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImag
m.mutex.Lock()
defer m.mutex.Unlock()
myID := contactIDFromPublicKey(&m.identity.PublicKey)
if _, err := m.sendContactUpdate(ctx, myID, ensName, profileImage); err != nil {
return err
}
// TODO: This should not be sending paired messages, as we do it above
for _, contact := range m.allContacts {
if contact.IsAdded() {
if _, err := m.sendContactUpdate(ctx, contact.ID, ensName, profileImage); err != nil {
@ -1353,7 +1369,6 @@ func (m *Messenger) SendContactUpdates(ctx context.Context, ensName, profileImag
}
}
return nil
}
// SendContactUpdate sends a contact update to a user and adds the user to contacts
@ -1417,7 +1432,7 @@ func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, prof
return nil, err
}
if !contact.IsAdded() {
if !contact.IsAdded() && contact.ID != contactIDFromPublicKey(&m.identity.PublicKey) {
contact.SystemTags = append(contact.SystemTags, contactAdded)
}
@ -1432,6 +1447,36 @@ func (m *Messenger) sendContactUpdate(ctx context.Context, chatID, ensName, prof
return &response, m.saveContact(contact)
}
// SyncDevices sends all public chats and contacts to paired devices
func (m *Messenger) SyncDevices(ctx context.Context, ensName, photoPath string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
myID := contactIDFromPublicKey(&m.identity.PublicKey)
if _, err := m.sendContactUpdate(ctx, myID, ensName, photoPath); err != nil {
return err
}
for _, chat := range m.allChats {
if chat.Public() && chat.Active {
if err := m.syncPublicChat(ctx, chat); err != nil {
return err
}
}
}
for _, contact := range m.allContacts {
if contact.IsAdded() && contact.ID != myID {
if err := m.syncContact(ctx, contact); err != nil {
return err
}
}
}
return nil
}
// SendPairInstallation sends a pair installation message
func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerResponse, error) {
m.mutex.Lock()
@ -1449,7 +1494,7 @@ func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerRespons
return nil, errors.New("no installation metadata")
}
chatID := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
chat, ok := m.allChats[chatID]
if !ok {
@ -1491,13 +1536,54 @@ func (m *Messenger) SendPairInstallation(ctx context.Context) (*MessengerRespons
return &response, nil
}
// syncPublicChat sync a public chat with paired devices
func (m *Messenger) syncPublicChat(ctx context.Context, publicChat *Chat) error {
var err error
if !m.hasPairedDevices() {
return nil
}
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
chat, ok := m.allChats[chatID]
if !ok {
chat = OneToOneFromPublicKey(&m.identity.PublicKey)
// We don't want to show the chat to the user
chat.Active = false
}
m.allChats[chat.ID] = chat
clock, _ := chat.NextClockAndTimestamp()
syncMessage := &protobuf.SyncInstallationPublicChat{
Clock: clock,
Id: publicChat.ID,
}
encodedMessage, err := proto.Marshal(syncMessage)
if err != nil {
return err
}
_, err = m.dispatchMessage(ctx, &RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_SYNC_INSTALLATION_PUBLIC_CHAT,
ResendAutomatically: true,
})
if err != nil {
return err
}
chat.LastClockValue = clock
return m.saveChat(chat)
}
// syncContact sync as contact with paired devices
func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
var err error
if !m.hasPairedDevices() {
return nil
}
chatID := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
chatID := contactIDFromPublicKey(&m.identity.PublicKey)
chat, ok := m.allChats[chatID]
if !ok {
@ -1624,7 +1710,7 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
publicKey := msg.SigPubKey()
// Check for messages from blocked users
senderID := types.EncodeHex(crypto.FromECDSAPub(publicKey))
senderID := contactIDFromPublicKey(publicKey)
if _, ok := messageState.AllContacts[senderID]; ok && messageState.AllContacts[senderID].IsBlocked() {
continue
}
@ -1706,6 +1792,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
logger.Warn("failed to handle SyncInstallationContact", zap.Error(err))
continue
}
case protobuf.SyncInstallationPublicChat:
if !isPubKeyEqual(messageState.CurrentMessageState.PublicKey, &m.identity.PublicKey) {
logger.Warn("not coming from us, ignoring")
continue
}
p := msg.ParsedMessage.(protobuf.SyncInstallationPublicChat)
logger.Debug("Handling SyncInstallationPublicChat", zap.Any("message", p))
err = m.handler.HandleSyncInstallationPublicChat(messageState, p)
if err != nil {
logger.Warn("failed to handle SyncInstallationPublicChat", zap.Error(err))
continue
}
case protobuf.RequestAddressForTransaction:
command := msg.ParsedMessage.(protobuf.RequestAddressForTransaction)
logger.Debug("Handling RequestAddressForTransaction", zap.Any("message", command))
@ -2479,7 +2578,7 @@ func (m *Messenger) AcceptRequestTransaction(ctx context.Context, transactionHas
return &response, m.saveChat(chat)
}
func (m *Messenger) SendTransaction(ctx context.Context, chatID, transactionHash string, signature []byte) (*MessengerResponse, error) {
func (m *Messenger) SendTransaction(ctx context.Context, chatID, value, contract, transactionHash string, signature []byte) (*MessengerResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
@ -2534,6 +2633,8 @@ func (m *Messenger) SendTransaction(ctx context.Context, chatID, transactionHash
message.ID = types.EncodeHex(newMessageID)
message.CommandParameters = &CommandParameters{
TransactionHash: transactionHash,
Value: value,
Contract: contract,
Signature: signature,
CommandState: CommandStateTransactionSent,
}
@ -2584,7 +2685,7 @@ func (m *Messenger) ValidateTransactions(ctx context.Context, addresses []types.
}
for _, validationResult := range responses {
var message *Message
chatID := types.EncodeHex(crypto.FromECDSAPub(validationResult.Transaction.From))
chatID := contactIDFromPublicKey(validationResult.Transaction.From)
chat, ok := m.allChats[chatID]
if !ok {
chat = OneToOneFromPublicKey(validationResult.Transaction.From)

View File

@ -342,10 +342,6 @@ func (g Group) LastClockValue() uint64 {
return g.events[len(g.events)-1].ClockValue
}
func (g Group) NextClockValue() uint64 {
return g.LastClockValue() + 1
}
func (g Group) creator() (string, error) {
if len(g.events) == 0 {
return "", errors.New("no events in the group")

View File

@ -53,6 +53,7 @@ type sockaddrInet6 struct {
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x30
sizeofMmsghdr = 0x38
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -47,6 +47,7 @@ type sockaddrInet6 struct {
const (
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofMmsghdr = 0x20
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -50,6 +50,7 @@ type sockaddrInet6 struct {
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -45,9 +45,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofMmsghdr = 0x20
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -48,9 +48,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -45,9 +45,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofMmsghdr = 0x20
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -48,9 +48,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -48,9 +48,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -45,9 +45,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofMmsghdr = 0x20
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -48,9 +48,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -48,9 +48,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -49,9 +49,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -48,9 +48,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofIovec = 0x10
sizeofMsghdr = 0x38
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0x10
sizeofSockaddrInet = 0x10

View File

@ -47,9 +47,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofMmsghdr = 0x20
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -50,9 +50,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x30
sizeofIovec = 0x10
sizeofMsghdr = 0x30
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -47,9 +47,9 @@ type sockaddrInet6 struct {
}
const (
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofIovec = 0x8
sizeofMsghdr = 0x1c
sizeofMmsghdr = 0x20
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -52,6 +52,7 @@ type sockaddrInet6 struct {
const (
sizeofIovec = 0x10
sizeofMsghdr = 0x30
sizeofMmsghdr = 0x40
sizeofCmsghdr = 0xc
sizeofSockaddrInet = 0x10

View File

@ -60,7 +60,8 @@ causes Load to run in LoadFiles mode, collecting minimal information.
See the documentation for type Config for details.
As noted earlier, the Config.Mode controls the amount of detail
reported about the loaded packages. See the documentation for type LoadMode
reported about the loaded packages, with each mode returning all the data of the
previous mode with some extra added. See the documentation for type LoadMode
for details.
Most tools should pass their command-line arguments (after any flags)

View File

@ -84,14 +84,13 @@ func findExternalDriver(cfg *Config) driver {
cmd.Stdin = bytes.NewReader(req)
cmd.Stdout = buf
cmd.Stderr = stderr
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
}
if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" {
fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr)
}
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr)
}
var response driverResponse
if err := json.Unmarshal(buf.Bytes(), &response); err != nil {
return nil, err

View File

@ -26,6 +26,7 @@ import (
"golang.org/x/tools/go/internal/packagesdriver"
"golang.org/x/tools/internal/gopathwalk"
"golang.org/x/tools/internal/semver"
"golang.org/x/tools/internal/span"
)
// debug controls verbose logging.
@ -253,7 +254,12 @@ func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDedu
if len(pkgs) == 0 {
return nil
}
dr, err := driver(cfg, pkgs...)
drivercfg := *cfg
if getGoInfo().env.modulesOn {
drivercfg.BuildFlags = append(drivercfg.BuildFlags, "-mod=readonly")
}
dr, err := driver(&drivercfg, pkgs...)
if err != nil {
return err
}
@ -264,7 +270,10 @@ func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDedu
if err != nil {
return err
}
return addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo)
if err := addNeededOverlayPackages(cfg, driver, response, needPkgs, getGoInfo); err != nil {
return err
}
return nil
}
func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string, goInfo func() *goInfo) error {
@ -278,43 +287,42 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err)
}
dirResponse, err := driver(cfg, pattern)
if err != nil || (len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].Errors) == 1) {
// There was an error loading the package. Try to load the file as an ad-hoc package.
// Usually the error will appear in a returned package, but may not if we're in modules mode
// and the ad-hoc is located outside a module.
if err != nil {
var queryErr error
dirResponse, queryErr = driver(cfg, query)
if queryErr != nil {
// Return the original error if the attempt to fall back failed.
return err
if dirResponse, queryErr = adHocPackage(cfg, driver, pattern, query); queryErr != nil {
return err // return the original error
}
// If we get nothing back from `go list`, try to make this file into its own ad-hoc package.
if len(dirResponse.Packages) == 0 && queryErr == 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" ||
filepath.ToSlash(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}
}
}
}
// `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))
@ -342,6 +350,74 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q
return nil
}
// adHocPackage attempts to construct an ad-hoc package given a query that failed.
func adHocPackage(cfg *Config, driver driver, pattern, query string) (*driverResponse, error) {
// There was an error loading the package. Try to load the file as an ad-hoc package.
// Usually the error will appear in a returned package, but may not if we're in modules mode
// and the ad-hoc is located outside a module.
dirResponse, err := driver(cfg, query)
if err != nil {
return nil, err
}
// If we get nothing back from `go list`, try to make this file into its own ad-hoc package.
if len(dirResponse.Packages) == 0 && err == nil {
dirResponse.Packages = append(dirResponse.Packages, &Package{
ID: "command-line-arguments",
PkgPath: query,
GoFiles: []string{query},
CompiledGoFiles: []string{query},
Imports: make(map[string]*Package),
})
dirResponse.Roots = append(dirResponse.Roots, "command-line-arguments")
}
// Special case to handle issue #33482:
// If this is a file= query for ad-hoc packages where the file only exists on an overlay,
// and exists outside of a module, add the file in for the package.
if len(dirResponse.Packages) == 1 && (dirResponse.Packages[0].ID == "command-line-arguments" || dirResponse.Packages[0].PkgPath == filepath.ToSlash(query)) {
if len(dirResponse.Packages[0].GoFiles) == 0 {
filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath
// TODO(matloob): check if the file is outside of a root dir?
for path := range cfg.Overlay {
if path == filename {
dirResponse.Packages[0].Errors = nil
dirResponse.Packages[0].GoFiles = []string{path}
dirResponse.Packages[0].CompiledGoFiles = []string{path}
}
}
}
}
return dirResponse, nil
}
func contains(files []string, filename string) bool {
for _, f := range files {
if f == filename {
return true
}
}
return false
}
// errorSpan attempts to parse a standard `go list` error message
// by stripping off the trailing error message.
//
// It works only on errors whose message is prefixed by colon,
// followed by a space (": "). For example:
//
// attributes.go:13:1: expected 'package', found 'type'
//
func errorSpan(err Error) span.Span {
if err.Pos == "" {
input := strings.TrimSpace(err.Msg)
msgIndex := strings.Index(input, ": ")
if msgIndex < 0 {
return span.Parse(input)
}
return span.Parse(input[:msgIndex])
}
return span.Parse(err.Pos)
}
// modCacheRegexp splits a path in a module cache into module, module version, and package.
var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`)
@ -805,14 +881,9 @@ func golistDriver(cfg *Config, rootsDirs func() *goInfo, words ...string) (*driv
}
if p.Error != nil {
msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363.
// Address golang.org/issue/35964 by appending import stack to error message.
if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 {
msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack)
}
pkg.Errors = append(pkg.Errors, Error{
Pos: p.Error.Pos,
Msg: msg,
Msg: strings.TrimSpace(p.Error.Err), // Trim to work around golang.org/issue/32363.
})
}

View File

@ -1,57 +0,0 @@
// 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 packages
import (
"fmt"
"strings"
)
var allModes = []LoadMode{
NeedName,
NeedFiles,
NeedCompiledGoFiles,
NeedImports,
NeedDeps,
NeedExportsFile,
NeedTypes,
NeedSyntax,
NeedTypesInfo,
NeedTypesSizes,
}
var modeStrings = []string{
"NeedName",
"NeedFiles",
"NeedCompiledGoFiles",
"NeedImports",
"NeedDeps",
"NeedExportsFile",
"NeedTypes",
"NeedSyntax",
"NeedTypesInfo",
"NeedTypesSizes",
}
func (mod LoadMode) String() string {
m := mod
if m == 0 {
return fmt.Sprintf("LoadMode(0)")
}
var out []string
for i, x := range allModes {
if x > m {
break
}
if (m & x) != 0 {
out = append(out, modeStrings[i])
m = m ^ x
}
}
if m != 0 {
out = append(out, "Unknown")
}
return fmt.Sprintf("LoadMode(%s)", strings.Join(out, "|"))
}

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
}

View File

@ -1,11 +1,13 @@
GOCMD=go
linters-install:
$(GOCMD) get -u github.com/alecthomas/gometalinter
gometalinter --install
@golangci-lint --version >/dev/null 2>&1 || { \
echo "installing linting tools..."; \
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s v1.19.1; \
}
lint: linters-install
gometalinter --vendor --disable-all --enable=vet --enable=vetshadow --enable=golint --enable=maligned --enable=megacheck --enable=ineffassign --enable=misspell --enable=errcheck --enable=goconst ./...
golangci-lint run
test:
$(GOCMD) test -cover -race ./...

View File

@ -1,7 +1,9 @@
**NOTICE:** v9 has entered maintenance status as of 2019-12-24. Please make all new functionality PR's against master.
Package validator
================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-9.29.1-green.svg)
![Project status](https://img.shields.io/badge/version-9.31.0-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)

View File

@ -103,6 +103,7 @@ var (
"rgba": isRGBA,
"hsl": isHSL,
"hsla": isHSLA,
"e164": isE164,
"email": isEmail,
"url": isURL,
"uri": isURI,
@ -224,14 +225,28 @@ func isOneOf(fl FieldLevel) bool {
func isUnique(fl FieldLevel) bool {
field := fl.Field()
param := fl.Param()
v := reflect.ValueOf(struct{}{})
switch field.Kind() {
case reflect.Slice, reflect.Array:
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
if param == "" {
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i), v)
}
return field.Len() == m.Len()
}
sf, ok := field.Type().Elem().FieldByName(param)
if !ok {
panic(fmt.Sprintf("Bad field name %s", param))
}
m := reflect.MakeMap(reflect.MapOf(sf.Type, v.Type()))
for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i), v)
m.SetMapIndex(field.Index(i).FieldByName(param), v)
}
return field.Len() == m.Len()
case reflect.Map:
@ -1219,6 +1234,11 @@ func isFile(fl FieldLevel) bool {
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
// IsE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
func isE164(fl FieldLevel) bool {
return e164Regex.MatchString(fl.Field().String())
}
// IsEmail is the validation function for validating if the current field's value is a valid email address.
func isEmail(fl FieldLevel) bool {
return emailRegex.MatchString(fl.Field().String())
@ -1301,19 +1321,7 @@ func isDefault(fl FieldLevel) bool {
// HasValue is the validation function for validating if the current field's value is not the default static value.
func hasValue(fl FieldLevel) bool {
return requireCheckFieldKind(fl, "")
}
// requireCheckField is a func for check field kind
func requireCheckFieldKind(fl FieldLevel, param string) bool {
field := fl.Field()
if len(param) > 0 {
if fl.Parent().Kind() == reflect.Ptr {
field = fl.Parent().Elem().FieldByName(param)
} else {
field = fl.Parent().FieldByName(param)
}
}
switch field.Kind() {
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return !field.IsNil()
@ -1325,79 +1333,73 @@ func requireCheckFieldKind(fl FieldLevel, param string) bool {
}
}
// requireCheckField is a func for check field kind
func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue bool) bool {
field := fl.Field()
kind := field.Kind()
var nullable, found bool
if len(param) > 0 {
field, kind, nullable, found = fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
if !found {
return defaultNotFoundValue
}
}
switch kind {
case reflect.Invalid:
return defaultNotFoundValue
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return field.IsNil()
default:
if nullable && field.Interface() != nil {
return false
}
return field.IsValid() && field.Interface() == reflect.Zero(field.Type()).Interface()
}
}
// RequiredWith is the validation function
// The field under validation must be present and not empty only if any of the other specified fields are present.
func requiredWith(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if requireCheckFieldKind(fl, param) {
return requireCheckFieldKind(fl, "")
if !requireCheckFieldKind(fl, param, true) {
return hasValue(fl)
}
}
return true
}
// RequiredWithAll is the validation function
// The field under validation must be present and not empty only if all of the other specified fields are present.
func requiredWithAll(fl FieldLevel) bool {
isValidateCurrentField := true
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if !requireCheckFieldKind(fl, param) {
isValidateCurrentField = false
if requireCheckFieldKind(fl, param, true) {
return true
}
}
if isValidateCurrentField {
return requireCheckFieldKind(fl, "")
}
return true
return hasValue(fl)
}
// RequiredWithout is the validation function
// The field under validation must be present and not empty only when any of the other specified fields are not present.
func requiredWithout(fl FieldLevel) bool {
isValidateCurrentField := false
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if requireCheckFieldKind(fl, param) {
isValidateCurrentField = true
}
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
return hasValue(fl)
}
if !isValidateCurrentField {
return requireCheckFieldKind(fl, "")
}
return true
}
// RequiredWithoutAll is the validation function
// The field under validation must be present and not empty only when all of the other specified fields are not present.
func requiredWithoutAll(fl FieldLevel) bool {
isValidateCurrentField := true
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if requireCheckFieldKind(fl, param) {
isValidateCurrentField = false
if !requireCheckFieldKind(fl, param, true) {
return true
}
}
if isValidateCurrentField {
return requireCheckFieldKind(fl, "")
}
return true
return hasValue(fl)
}
// IsGteField is the validation function for validating if the current field's value is greater than or equal to the field specified by the param's value.

View File

@ -39,9 +39,7 @@ func (sc *structCache) Get(key reflect.Type) (c *cStruct, found bool) {
}
func (sc *structCache) Set(key reflect.Type, value *cStruct) {
m := sc.m.Load().(map[reflect.Type]*cStruct)
nm := make(map[reflect.Type]*cStruct, len(m)+1)
for k, v := range m {
nm[k] = v
@ -61,9 +59,7 @@ func (tc *tagCache) Get(key string) (c *cTag, found bool) {
}
func (tc *tagCache) Set(key string, value *cTag) {
m := tc.m.Load().(map[string]*cTag)
nm := make(map[string]*cTag, len(m)+1)
for k, v := range m {
nm[k] = v
@ -87,22 +83,22 @@ type cField struct {
}
type cTag struct {
tag string
aliasTag string
actualAliasTag string
param string
keys *cTag // only populated when using tag's 'keys' and 'endkeys' for map key validation
next *cTag
fn FuncCtx
typeof tagType
hasTag bool
hasAlias bool
hasParam bool // true if parameter used eg. eq= where the equal sign has been set
isBlockEnd bool // indicates the current tag represents the last validation in the block
tag string
aliasTag string
actualAliasTag string
param string
keys *cTag // only populated when using tag's 'keys' and 'endkeys' for map key validation
next *cTag
fn FuncCtx
typeof tagType
hasTag bool
hasAlias bool
hasParam bool // true if parameter used eg. eq= where the equal sign has been set
isBlockEnd bool // indicates the current tag represents the last validation in the block
runValidationWhenNil bool
}
func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStruct {
v.structCache.lock.Lock()
defer v.structCache.lock.Unlock() // leave as defer! because if inner panics, it will never get unlocked otherwise!
@ -141,9 +137,7 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
customName = fld.Name
if v.hasTagNameFunc {
name := v.tagNameFunc(fld)
if len(name) > 0 {
customName = name
}
@ -168,23 +162,17 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
namesEqual: fld.Name == customName,
})
}
v.structCache.Set(typ, cs)
return cs
}
func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias string, hasAlias bool) (firstCtag *cTag, current *cTag) {
var t string
var ok bool
noAlias := len(alias) == 0
tags := strings.Split(tag, tagSeparator)
for i := 0; i < len(tags); i++ {
t = tags[i]
if noAlias {
alias = t
}
@ -198,14 +186,13 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
current.next, current = next, curr
}
continue
}
var prevTag tagType
if i == 0 {
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true}
current = &cTag{aliasTag: alias, hasAlias: hasAlias, hasTag: true, typeof: typeDefault}
firstCtag = current
} else {
prevTag = current.typeof
@ -214,7 +201,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
}
switch t {
case diveTag:
current.typeof = typeDive
continue
@ -270,18 +256,14 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
continue
default:
if t == isdefault {
current.typeof = typeIsDefault
}
// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)
for j := 0; j < len(orVals); j++ {
vals := strings.SplitN(orVals[j], tagKeySeparator, 2)
if noAlias {
alias = vals[0]
current.aliasTag = alias
@ -300,7 +282,10 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
panic(strings.TrimSpace(fmt.Sprintf(invalidValidation, fieldName)))
}
if current.fn, ok = v.validations[current.tag]; !ok {
if wrapper, ok := v.validations[current.tag]; ok {
current.fn = wrapper.fn
current.runValidationWhenNil = wrapper.runValidatinOnNil
} else {
panic(strings.TrimSpace(fmt.Sprintf(undefinedValidation, current.tag, fieldName)))
}

View File

@ -585,9 +585,15 @@ Unique
For arrays & slices, unique will ensure that there are no duplicates.
For maps, unique will ensure that there are no duplicate values.
For slices of struct, unique will ensure that there are no duplicate values
in a field of the struct specified via a parameter.
// For arrays, slices, and maps:
Usage: unique
// For slices of struct:
Usage: unique=field
Alpha Only
This validates that a string value contains ASCII alpha characters only
@ -1058,6 +1064,35 @@ Validator notes:
And the best reason, you can submit a pull request and we can keep on
adding to the validation library of this package!
Non standard validators
A collection of validation rules that are frequently needed but are more
complex than the ones found in the baked in validators.
A non standard validator must be registered manually like you would
with your own custom validation functions.
Example of registration and use:
type Test struct {
TestField string `validate:"yourtag"`
}
t := &Test{
TestField: "Test"
}
validate := validator.New()
validate.RegisterValidation("yourtag", validators.NotBlank)
Here is a list of the current non standard validators:
NotBlank
This validates that the value is not blank or with length zero.
For strings ensures they do not contain only spaces. For channels, maps, slices and arrays
ensures they don't have zero length. For others, a non empty value is required.
Usage: notblank
Panics
This package panics when bad input is provided, this is by design, bad code like
@ -1072,30 +1107,5 @@ that should not make it to production.
}
validate.Struct(t) // this will panic
Non standard validators
A collection of validation rules that are frequently needed but are more
complex than the ones found in the baked in validators.
A non standard validator must be registered manually using any tag you like.
See below examples of registration and use.
type Test struct {
TestField string `validate:"yourtag"`
}
t := &Test{
TestField: "Test"
}
validate := validator.New()
validate.RegisterValidation("yourtag", validations.ValidatorName)
NotBlank
This validates that the value is not blank or with length zero.
For strings ensures they do not contain only spaces. For channels, maps, slices and arrays
ensures they don't have zero length. For others, a non empty value is required.
Usage: notblank
*/
package validator

View File

@ -5,7 +5,6 @@ import "reflect"
// FieldLevel contains all the information and helper functions
// to validate a field
type FieldLevel interface {
// returns the top level struct, if any
Top() reflect.Value
@ -26,6 +25,9 @@ type FieldLevel interface {
// returns param for validation against current field
Param() string
// GetTag returns the current validations tag name
GetTag() string
// ExtractType gets the actual underlying type of field value.
// It will dive into pointers, customTypes and return you the
// underlying value and it's kind.
@ -37,7 +39,27 @@ type FieldLevel interface {
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
//
// Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable.
GetStructFieldOK() (reflect.Value, reflect.Kind, bool)
// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
//
// Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable.
GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool)
// traverses the parent struct to retrieve a specific field denoted by the provided namespace
// in the param and returns the field, field kind, if it's a nullable type and whether is was successful in retrieving
// the field at all.
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool)
// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool)
}
var _ FieldLevel = new(validate)
@ -48,11 +70,16 @@ func (v *validate) Field() reflect.Value {
}
// FieldName returns the field's name with the tag
// name takeing precedence over the fields actual name.
// name taking precedence over the fields actual name.
func (v *validate) FieldName() string {
return v.cf.altName
}
// GetTag returns the current validations tag name
func (v *validate) GetTag() string {
return v.ct.tag
}
// StructFieldName returns the struct field's name
func (v *validate) StructFieldName() string {
return v.cf.name
@ -64,6 +91,29 @@ func (v *validate) Param() string {
}
// GetStructFieldOK returns Param returns param for validation against current field
//
// Deprecated: Use GetStructFieldOK2() instead which also return if the value is nullable.
func (v *validate) GetStructFieldOK() (reflect.Value, reflect.Kind, bool) {
current, kind, _, found := v.getStructFieldOKInternal(v.slflParent, v.ct.param)
return current, kind, found
}
// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
//
// Deprecated: Use GetStructFieldOKAdvanced2() instead which also return if the value is nullable.
func (v *validate) GetStructFieldOKAdvanced(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool) {
current, kind, _, found := v.GetStructFieldOKAdvanced2(val, namespace)
return current, kind, found
}
// GetStructFieldOK returns Param returns param for validation against current field
func (v *validate) GetStructFieldOK2() (reflect.Value, reflect.Kind, bool, bool) {
return v.getStructFieldOKInternal(v.slflParent, v.ct.param)
}
// GetStructFieldOKAdvanced is the same as GetStructFieldOK except that it accepts the parent struct to start looking for
// the field and namespace allowing more extensibility for validators.
func (v *validate) GetStructFieldOKAdvanced2(val reflect.Value, namespace string) (reflect.Value, reflect.Kind, bool, bool) {
return v.getStructFieldOKInternal(val, namespace)
}

View File

@ -16,6 +16,7 @@ const (
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
e164RegexString = "^\\+[1-9]?[0-9]{7,14}$"
base64RegexString = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$"
base64URLRegexString = "^(?:[A-Za-z0-9-_]{4})*(?:[A-Za-z0-9-_]{2}==|[A-Za-z0-9-_]{3}=|[A-Za-z0-9-_]{4})$"
iSBN10RegexString = "^(?:[0-9]{9}X|[0-9]{10})$"
@ -61,6 +62,7 @@ var (
rgbaRegex = regexp.MustCompile(rgbaRegexString)
hslRegex = regexp.MustCompile(hslRegexString)
hslaRegex = regexp.MustCompile(hslaRegexString)
e164Regex = regexp.MustCompile(e164RegexString)
emailRegex = regexp.MustCompile(emailRegexString)
base64Regex = regexp.MustCompile(base64RegexString)
base64URLRegex = regexp.MustCompile(base64URLRegexString)

View File

@ -57,11 +57,10 @@ BEGIN:
//
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist.
func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, found bool) {
func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) {
BEGIN:
current, kind, _ = v.ExtractType(val)
current, kind, nullable = v.ExtractType(val)
if kind == reflect.Invalid {
return
}
@ -112,7 +111,7 @@ BEGIN:
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
if arrIdx >= current.Len() {
return current, kind, false
return
}
startIdx := idx2 + 1

View File

@ -7,7 +7,7 @@ import (
"strconv"
)
// per validate contruct
// per validate construct
type validate struct {
v *Validate
top reflect.Value
@ -94,7 +94,6 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur
// traverseField validates any field, be it a struct or single field, ensures it's validity and passes it along to be validated via it's tag options
func (v *validate) traverseField(ctx context.Context, parent reflect.Value, current reflect.Value, ns []byte, structNs []byte, cf *cField, ct *cTag) {
var typ reflect.Type
var kind reflect.Kind
@ -112,16 +111,13 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
}
if ct.hasTag {
v.str1 = string(append(ns, cf.altName...))
if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
} else {
v.str2 = v.str1
}
if kind == reflect.Invalid {
v.str1 = string(append(ns, cf.altName...))
if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
} else {
v.str2 = v.str1
}
v.errs = append(v.errs,
&fieldError{
v: v.v,
@ -135,27 +131,33 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
kind: kind,
},
)
return
}
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: ct.aliasTag,
actualTag: ct.tag,
ns: v.str1,
structNs: v.str2,
fieldLen: uint8(len(cf.altName)),
structfieldLen: uint8(len(cf.name)),
value: current.Interface(),
param: ct.param,
kind: kind,
typ: current.Type(),
},
)
return
v.str1 = string(append(ns, cf.altName...))
if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
} else {
v.str2 = v.str1
}
if !ct.runValidationWhenNil {
v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: ct.aliasTag,
actualTag: ct.tag,
ns: v.str1,
structNs: v.str2,
fieldLen: uint8(len(cf.altName)),
structfieldLen: uint8(len(cf.name)),
value: current.Interface(),
param: ct.param,
kind: kind,
typ: current.Type(),
},
)
return
}
}
case reflect.Struct:

View File

@ -13,27 +13,31 @@ import (
)
const (
defaultTagName = "validate"
utf8HexComma = "0x2C"
utf8Pipe = "0x7C"
tagSeparator = ","
orSeparator = "|"
tagKeySeparator = "="
structOnlyTag = "structonly"
noStructLevelTag = "nostructlevel"
omitempty = "omitempty"
isdefault = "isdefault"
skipValidationTag = "-"
diveTag = "dive"
keysTag = "keys"
endKeysTag = "endkeys"
requiredTag = "required"
namespaceSeparator = "."
leftBracket = "["
rightBracket = "]"
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
defaultTagName = "validate"
utf8HexComma = "0x2C"
utf8Pipe = "0x7C"
tagSeparator = ","
orSeparator = "|"
tagKeySeparator = "="
structOnlyTag = "structonly"
noStructLevelTag = "nostructlevel"
omitempty = "omitempty"
isdefault = "isdefault"
requiredWithoutAllTag = "required_without_all"
requiredWithoutTag = "required_without"
requiredWithTag = "required_with"
requiredWithAllTag = "required_with_all"
skipValidationTag = "-"
diveTag = "dive"
keysTag = "keys"
endKeysTag = "endkeys"
requiredTag = "required"
namespaceSeparator = "."
leftBracket = "["
rightBracket = "]"
restrictedTagChars = ".[],|=+()`~!@#$%^&*\\\"/?<>{}"
restrictedAliasErr = "Alias '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
restrictedTagErr = "Tag '%s' either contains restricted characters or is the same as a restricted tag needed for normal operation"
)
var (
@ -55,6 +59,11 @@ type CustomTypeFunc func(field reflect.Value) interface{}
// TagNameFunc allows for adding of a custom tag name parser
type TagNameFunc func(field reflect.StructField) string
type internalValidationFuncWrapper struct {
fn FuncCtx
runValidatinOnNil bool
}
// Validate contains the validator settings and cache
type Validate struct {
tagName string
@ -65,7 +74,7 @@ type Validate struct {
structLevelFuncs map[reflect.Type]StructLevelFuncCtx
customFuncs map[reflect.Type]CustomTypeFunc
aliases map[string]string
validations map[string]FuncCtx
validations map[string]internalValidationFuncWrapper
transTagFunc map[ut.Translator]map[string]TranslationFunc // map[<locale>]map[<tag>]TranslationFunc
tagCache *tagCache
structCache *structCache
@ -83,7 +92,7 @@ func New() *Validate {
v := &Validate{
tagName: defaultTagName,
aliases: make(map[string]string, len(bakedInAliases)),
validations: make(map[string]FuncCtx, len(bakedInValidators)),
validations: make(map[string]internalValidationFuncWrapper, len(bakedInValidators)),
tagCache: tc,
structCache: sc,
}
@ -96,8 +105,14 @@ func New() *Validate {
// must copy validators for separate validations to be used in each instance
for k, val := range bakedInValidators {
// no need to error check here, baked in will always be valid
_ = v.registerValidation(k, wrapFunc(val), true)
switch k {
// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour
case requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag:
_ = v.registerValidation(k, wrapFunc(val), true, true)
default:
// no need to error check here, baked in will always be valid
_ = v.registerValidation(k, wrapFunc(val), true, false)
}
}
v.pool = &sync.Pool{
@ -140,18 +155,21 @@ func (v *Validate) RegisterTagNameFunc(fn TagNameFunc) {
// NOTES:
// - if the key already exists, the previous validation function will be replaced.
// - this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterValidation(tag string, fn Func) error {
return v.RegisterValidationCtx(tag, wrapFunc(fn))
func (v *Validate) RegisterValidation(tag string, fn Func, callValidationEvenIfNull ...bool) error {
return v.RegisterValidationCtx(tag, wrapFunc(fn), callValidationEvenIfNull...)
}
// RegisterValidationCtx does the same as RegisterValidation on accepts a FuncCtx validation
// allowing context.Context validation support.
func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx) error {
return v.registerValidation(tag, fn, false)
func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx, callValidationEvenIfNull ...bool) error {
var nilCheckable bool
if len(callValidationEvenIfNull) > 0 {
nilCheckable = callValidationEvenIfNull[0]
}
return v.registerValidation(tag, fn, false, nilCheckable)
}
func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool) error {
func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool, nilCheckable bool) error {
if len(tag) == 0 {
return errors.New("Function Key cannot be empty")
}
@ -161,13 +179,10 @@ func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool) erro
}
_, ok := restrictedTags[tag]
if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) {
panic(fmt.Sprintf(restrictedTagErr, tag))
}
v.validations[tag] = fn
v.validations[tag] = internalValidationFuncWrapper{fn: fn, runValidatinOnNil: nilCheckable}
return nil
}

9
vendor/modules.txt vendored
View File

@ -133,7 +133,7 @@ github.com/golang/protobuf/proto
github.com/golang/protobuf/protoc-gen-go/descriptor
# github.com/golang/snappy v0.0.1
github.com/golang/snappy
# github.com/gomarkdown/markdown v0.0.0-20191104174740-4d42851d4d5a => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
# github.com/gomarkdown/markdown v0.0.0-20191209105822-e3ba6c6109ba => github.com/status-im/markdown v0.0.0-20191209105822-e3ba6c6109ba
github.com/gomarkdown/markdown
github.com/gomarkdown/markdown/ast
github.com/gomarkdown/markdown/parser
@ -504,7 +504,7 @@ golang.org/x/crypto/ssh/terminal
# golang.org/x/lint v0.0.0-20190930215403-16217165b5de
golang.org/x/lint
golang.org/x/lint/golint
# golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3
# golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2
golang.org/x/net/bpf
golang.org/x/net/context
golang.org/x/net/html
@ -541,7 +541,7 @@ golang.org/x/text/secure/bidirule
golang.org/x/text/transform
golang.org/x/text/unicode/bidi
golang.org/x/text/unicode/norm
# golang.org/x/tools v0.0.0-20191213032237-7093a17b0467
# golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101
golang.org/x/tools/go/analysis
golang.org/x/tools/go/analysis/passes/inspect
golang.org/x/tools/go/ast/astutil
@ -556,7 +556,8 @@ golang.org/x/tools/go/types/typeutil
golang.org/x/tools/internal/fastwalk
golang.org/x/tools/internal/gopathwalk
golang.org/x/tools/internal/semver
# gopkg.in/go-playground/validator.v9 v9.29.1
golang.org/x/tools/internal/span
# gopkg.in/go-playground/validator.v9 v9.31.0
gopkg.in/go-playground/validator.v9
# gopkg.in/natefinch/lumberjack.v2 v2.0.0
gopkg.in/natefinch/lumberjack.v2