mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-08-30 17:13:08 +08:00
fix(vendor): github dependabot alerts
This commit is contained in:
@@ -52,7 +52,7 @@ require (
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/gin-gonic/gin v1.5.0
|
||||
github.com/gin-gonic/gin v1.7.0
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 // indirect
|
||||
github.com/go-ini/ini v1.62.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.4.0 // indirect
|
||||
@@ -69,7 +69,7 @@ require (
|
||||
github.com/googollee/go-engine.io v0.0.0-20180829091931-e2f255711dcb // indirect
|
||||
github.com/googollee/go-socket.io v0.0.0-20181214084611-0ad7206c347a
|
||||
github.com/gorilla/mux v1.7.0
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
github.com/gorilla/websocket v1.4.1
|
||||
github.com/gosuri/uitable v0.0.0-20160404203958-36ee7e946282
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||
github.com/hako/durafmt v0.0.0-20180520121703-7b7ae1e72ead
|
||||
@@ -91,7 +91,7 @@ require (
|
||||
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7
|
||||
github.com/mdlayher/raw v0.0.0-20190606144222-a54781e5f38f
|
||||
github.com/mholt/caddy v0.10.11
|
||||
github.com/miekg/dns v1.1.4
|
||||
github.com/miekg/dns v1.1.25
|
||||
github.com/minio/minio-go v6.0.14+incompatible
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
|
||||
@@ -226,6 +226,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.5.0 h1:fi+bqFAx/oLK54somfCtEZs9HeH1LHVoEPUgARpTqyc=
|
||||
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
|
||||
github.com/gin-gonic/gin v1.7.0 h1:jGB9xAJQ12AIGNB4HguylppmDK1Am9ppF7XnGXXJuoU=
|
||||
github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
@@ -248,10 +250,17 @@ github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
@@ -335,6 +344,8 @@ github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8=
|
||||
github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0=
|
||||
github.com/gosuri/uitable v0.0.0-20160404203958-36ee7e946282 h1:KFqmdzEPbU7Uck2tn50t+HQXZNVkxe8M9qRb/ZoSHaE=
|
||||
@@ -375,6 +386,7 @@ github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
@@ -405,6 +417,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911 h1:FvnrqecqX4zT0wOIbYK1gNgTm0677INEWiFY8UEYggY=
|
||||
github.com/lestrrat-go/iter v0.0.0-20200422075355-fc1769541911/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.0.2 h1:FsbZg/v979RikHWhSu/7BRHh2Z1Z8byPleURRb1Y0XI=
|
||||
@@ -428,6 +442,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
@@ -457,6 +473,8 @@ github.com/mholt/caddy v0.10.11 h1:s8X+R8DuBbrrMuUTcWSxlDe567B0s5EDmiDBKSYsioY=
|
||||
github.com/mholt/caddy v0.10.11/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY=
|
||||
github.com/miekg/dns v1.1.4 h1:rCMZsU2ScVSYcAsOXgmC6+AKOK+6pmQTOcw03nfwYV0=
|
||||
github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg=
|
||||
github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc=
|
||||
github.com/minio/cli v1.20.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o=
|
||||
@@ -643,6 +661,7 @@ golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/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-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -694,6 +713,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -738,11 +758,14 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190830023255-19e00faab6ad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
@@ -774,6 +797,7 @@ golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgw
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
||||
+12
-4
@@ -3,12 +3,20 @@ language: go
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- go: 1.10.x
|
||||
- go: 1.11.x
|
||||
env: GO111MODULE=on
|
||||
- go: 1.12.x
|
||||
env: GO111MODULE=on
|
||||
- go: 1.13.x
|
||||
- go: 1.13.x
|
||||
env:
|
||||
- TESTTAGS=nomsgpack
|
||||
- go: 1.14.x
|
||||
- go: 1.14.x
|
||||
env:
|
||||
- TESTTAGS=nomsgpack
|
||||
- go: 1.15.x
|
||||
- go: 1.15.x
|
||||
env:
|
||||
- TESTTAGS=nomsgpack
|
||||
- go: master
|
||||
|
||||
git:
|
||||
@@ -18,7 +26,7 @@ before_install:
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then mkdir "${HOME}/go"; export GOPATH="${HOME}/go"; fi
|
||||
|
||||
install:
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; else make install; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then go mod download; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then export PATH="${GOPATH}/bin:${GOROOT}/bin:${PATH}"; fi
|
||||
- if [[ "${GO111MODULE}" = "on" ]]; then make tools; fi
|
||||
|
||||
|
||||
+3
-1
@@ -156,7 +156,7 @@ People and companies, who have contributed, in alphabetical order.
|
||||
- Fix variadic parameter in the flexible render API
|
||||
- Fix Corrupted plain render
|
||||
- Add Pluggable View Renderer Example
|
||||
|
||||
|
||||
|
||||
**@msemenistyi (Mykyta Semenistyi)**
|
||||
- update Readme.md. Add code to String method
|
||||
@@ -190,6 +190,8 @@ People and companies, who have contributed, in alphabetical order.
|
||||
**@rogierlommers (Rogier Lommers)**
|
||||
- Add updated static serve example
|
||||
|
||||
**@rw-access (Ross Wolf)**
|
||||
- Added support to mix exact and param routes
|
||||
|
||||
**@se77en (Damon Zhao)**
|
||||
- Improve color logging
|
||||
|
||||
+619
-557
File diff suppressed because it is too large
Load Diff
+123
-20
@@ -1,4 +1,107 @@
|
||||
### Gin v1.5.0
|
||||
# Gin ChangeLog
|
||||
|
||||
## Gin v1.7.0
|
||||
|
||||
### BUGFIXES
|
||||
|
||||
* fix compile error from [#2572](https://github.com/gin-gonic/gin/pull/2572) ([#2600](https://github.com/gin-gonic/gin/pull/2600))
|
||||
* fix: print headers without Authorization header on broken pipe ([#2528](https://github.com/gin-gonic/gin/pull/2528))
|
||||
* fix(tree): reassign fullpath when register new node ([#2366](https://github.com/gin-gonic/gin/pull/2366))
|
||||
|
||||
### ENHANCEMENTS
|
||||
|
||||
* Support params and exact routes without creating conflicts ([#2663](https://github.com/gin-gonic/gin/pull/2663))
|
||||
* chore: improve render string performance ([#2365](https://github.com/gin-gonic/gin/pull/2365))
|
||||
* Sync route tree to httprouter latest code ([#2368](https://github.com/gin-gonic/gin/pull/2368))
|
||||
* chore: rename getQueryCache/getFormCache to initQueryCache/initFormCa ([#2375](https://github.com/gin-gonic/gin/pull/2375))
|
||||
* chore(performance): improve countParams ([#2378](https://github.com/gin-gonic/gin/pull/2378))
|
||||
* Remove some functions that have the same effect as the bytes package ([#2387](https://github.com/gin-gonic/gin/pull/2387))
|
||||
* update:SetMode function ([#2321](https://github.com/gin-gonic/gin/pull/2321))
|
||||
* remove a unused type SecureJSONPrefix ([#2391](https://github.com/gin-gonic/gin/pull/2391))
|
||||
* Add a redirect sample for POST method ([#2389](https://github.com/gin-gonic/gin/pull/2389))
|
||||
* Add CustomRecovery builtin middleware ([#2322](https://github.com/gin-gonic/gin/pull/2322))
|
||||
* binding: avoid 2038 problem on 32-bit architectures ([#2450](https://github.com/gin-gonic/gin/pull/2450))
|
||||
* Prevent panic in Context.GetQuery() when there is no Request ([#2412](https://github.com/gin-gonic/gin/pull/2412))
|
||||
* Add GetUint and GetUint64 method on gin.context ([#2487](https://github.com/gin-gonic/gin/pull/2487))
|
||||
* update content-disposition header to MIME-style ([#2512](https://github.com/gin-gonic/gin/pull/2512))
|
||||
* reduce allocs and improve the render `WriteString` ([#2508](https://github.com/gin-gonic/gin/pull/2508))
|
||||
* implement ".Unwrap() error" on Error type ([#2525](https://github.com/gin-gonic/gin/pull/2525)) ([#2526](https://github.com/gin-gonic/gin/pull/2526))
|
||||
* Allow bind with a map[string]string ([#2484](https://github.com/gin-gonic/gin/pull/2484))
|
||||
* chore: update tree ([#2371](https://github.com/gin-gonic/gin/pull/2371))
|
||||
* Support binding for slice/array obj [Rewrite] ([#2302](https://github.com/gin-gonic/gin/pull/2302))
|
||||
* basic auth: fix timing oracle ([#2609](https://github.com/gin-gonic/gin/pull/2609))
|
||||
* Add mixed param and non-param paths (port of httprouter[#329](https://github.com/gin-gonic/gin/pull/329)) ([#2663](https://github.com/gin-gonic/gin/pull/2663))
|
||||
* feat(engine): add trustedproxies and remoteIP ([#2632](https://github.com/gin-gonic/gin/pull/2632))
|
||||
|
||||
## Gin v1.6.3
|
||||
|
||||
### ENHANCEMENTS
|
||||
|
||||
* Improve performance: Change `*sync.RWMutex` to `sync.RWMutex` in context. [#2351](https://github.com/gin-gonic/gin/pull/2351)
|
||||
|
||||
## Gin v1.6.2
|
||||
|
||||
### BUGFIXES
|
||||
* fix missing initial sync.RWMutex [#2305](https://github.com/gin-gonic/gin/pull/2305)
|
||||
### ENHANCEMENTS
|
||||
* Add set samesite in cookie. [#2306](https://github.com/gin-gonic/gin/pull/2306)
|
||||
|
||||
## Gin v1.6.1
|
||||
|
||||
### BUGFIXES
|
||||
* Revert "fix accept incoming network connections" [#2294](https://github.com/gin-gonic/gin/pull/2294)
|
||||
|
||||
## Gin v1.6.0
|
||||
|
||||
### BREAKING
|
||||
* chore(performance): Improve performance for adding RemoveExtraSlash flag [#2159](https://github.com/gin-gonic/gin/pull/2159)
|
||||
* drop support govendor [#2148](https://github.com/gin-gonic/gin/pull/2148)
|
||||
* Added support for SameSite cookie flag [#1615](https://github.com/gin-gonic/gin/pull/1615)
|
||||
### FEATURES
|
||||
* add yaml negotiation [#2220](https://github.com/gin-gonic/gin/pull/2220)
|
||||
* FileFromFS [#2112](https://github.com/gin-gonic/gin/pull/2112)
|
||||
### BUGFIXES
|
||||
* Unix Socket Handling [#2280](https://github.com/gin-gonic/gin/pull/2280)
|
||||
* Use json marshall in context json to fix breaking new line issue. Fixes #2209 [#2228](https://github.com/gin-gonic/gin/pull/2228)
|
||||
* fix accept incoming network connections [#2216](https://github.com/gin-gonic/gin/pull/2216)
|
||||
* Fixed a bug in the calculation of the maximum number of parameters [#2166](https://github.com/gin-gonic/gin/pull/2166)
|
||||
* [FIX] allow empty headers on DataFromReader [#2121](https://github.com/gin-gonic/gin/pull/2121)
|
||||
* Add mutex for protect Context.Keys map [#1391](https://github.com/gin-gonic/gin/pull/1391)
|
||||
### ENHANCEMENTS
|
||||
* Add mitigation for log injection [#2277](https://github.com/gin-gonic/gin/pull/2277)
|
||||
* tree: range over nodes values [#2229](https://github.com/gin-gonic/gin/pull/2229)
|
||||
* tree: remove duplicate assignment [#2222](https://github.com/gin-gonic/gin/pull/2222)
|
||||
* chore: upgrade go-isatty and json-iterator/go [#2215](https://github.com/gin-gonic/gin/pull/2215)
|
||||
* path: sync code with httprouter [#2212](https://github.com/gin-gonic/gin/pull/2212)
|
||||
* Use zero-copy approach to convert types between string and byte slice [#2206](https://github.com/gin-gonic/gin/pull/2206)
|
||||
* Reuse bytes when cleaning the URL paths [#2179](https://github.com/gin-gonic/gin/pull/2179)
|
||||
* tree: remove one else statement [#2177](https://github.com/gin-gonic/gin/pull/2177)
|
||||
* tree: sync httprouter update (#2173) (#2172) [#2171](https://github.com/gin-gonic/gin/pull/2171)
|
||||
* tree: sync part httprouter codes and reduce if/else [#2163](https://github.com/gin-gonic/gin/pull/2163)
|
||||
* use http method constant [#2155](https://github.com/gin-gonic/gin/pull/2155)
|
||||
* upgrade go-validator to v10 [#2149](https://github.com/gin-gonic/gin/pull/2149)
|
||||
* Refactor redirect request in gin.go [#1970](https://github.com/gin-gonic/gin/pull/1970)
|
||||
* Add build tag nomsgpack [#1852](https://github.com/gin-gonic/gin/pull/1852)
|
||||
### DOCS
|
||||
* docs(path): improve comments [#2223](https://github.com/gin-gonic/gin/pull/2223)
|
||||
* Renew README to fit the modification of SetCookie method [#2217](https://github.com/gin-gonic/gin/pull/2217)
|
||||
* Fix spelling [#2202](https://github.com/gin-gonic/gin/pull/2202)
|
||||
* Remove broken link from README. [#2198](https://github.com/gin-gonic/gin/pull/2198)
|
||||
* Update docs on Context.Done(), Context.Deadline() and Context.Err() [#2196](https://github.com/gin-gonic/gin/pull/2196)
|
||||
* Update validator to v10 [#2190](https://github.com/gin-gonic/gin/pull/2190)
|
||||
* upgrade go-validator to v10 for README [#2189](https://github.com/gin-gonic/gin/pull/2189)
|
||||
* Update to currently output [#2188](https://github.com/gin-gonic/gin/pull/2188)
|
||||
* Fix "Custom Validators" example [#2186](https://github.com/gin-gonic/gin/pull/2186)
|
||||
* Add project to README [#2165](https://github.com/gin-gonic/gin/pull/2165)
|
||||
* docs(benchmarks): for gin v1.5 [#2153](https://github.com/gin-gonic/gin/pull/2153)
|
||||
* Changed wording for clarity in README.md [#2122](https://github.com/gin-gonic/gin/pull/2122)
|
||||
### MISC
|
||||
* ci support go1.14 [#2262](https://github.com/gin-gonic/gin/pull/2262)
|
||||
* chore: upgrade depend version [#2231](https://github.com/gin-gonic/gin/pull/2231)
|
||||
* Drop support go1.10 [#2147](https://github.com/gin-gonic/gin/pull/2147)
|
||||
* fix comment in `mode.go` [#2129](https://github.com/gin-gonic/gin/pull/2129)
|
||||
|
||||
## Gin v1.5.0
|
||||
|
||||
- [FIX] Use DefaultWriter and DefaultErrorWriter for debug messages [#1891](https://github.com/gin-gonic/gin/pull/1891)
|
||||
- [NEW] Now you can parse the inline lowercase start structure [#1893](https://github.com/gin-gonic/gin/pull/1893)
|
||||
@@ -90,7 +193,7 @@
|
||||
- [NEW] Upgrade dependency libraries [#1491](https://github.com/gin-gonic/gin/pull/1491)
|
||||
|
||||
|
||||
### Gin v1.3.0
|
||||
## Gin v1.3.0
|
||||
|
||||
- [NEW] Add [`func (*Context) QueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.QueryMap), [`func (*Context) GetQueryMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetQueryMap), [`func (*Context) PostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.PostFormMap) and [`func (*Context) GetPostFormMap`](https://godoc.org/github.com/gin-gonic/gin#Context.GetPostFormMap) to support `type map[string]string` as query string or form parameters, see [#1383](https://github.com/gin-gonic/gin/pull/1383)
|
||||
- [NEW] Add [`func (*Context) AsciiJSON`](https://godoc.org/github.com/gin-gonic/gin#Context.AsciiJSON), see [#1358](https://github.com/gin-gonic/gin/pull/1358)
|
||||
@@ -112,7 +215,7 @@
|
||||
- [FIX] Gin Mode `""` when calling [`func Mode`](https://godoc.org/github.com/gin-gonic/gin#Mode) now returns `const DebugMode`, see [#1250](https://github.com/gin-gonic/gin/pull/1250)
|
||||
- [FIX] `Flush()` now doesn't overwrite `responseWriter` status code, see [#1460](https://github.com/gin-gonic/gin/pull/1460)
|
||||
|
||||
### Gin 1.2.0
|
||||
## Gin 1.2.0
|
||||
|
||||
- [NEW] Switch from godeps to govendor
|
||||
- [NEW] Add support for Let's Encrypt via gin-gonic/autotls
|
||||
@@ -135,25 +238,25 @@
|
||||
- [FIX] Use X-Forwarded-For before X-Real-Ip
|
||||
- [FIX] time.Time binding (#904)
|
||||
|
||||
### Gin 1.1.4
|
||||
## Gin 1.1.4
|
||||
|
||||
- [NEW] Support google appengine for IsTerminal func
|
||||
|
||||
### Gin 1.1.3
|
||||
## Gin 1.1.3
|
||||
|
||||
- [FIX] Reverted Logger: skip ANSI color commands
|
||||
|
||||
### Gin 1.1
|
||||
## Gin 1.1
|
||||
|
||||
- [NEW] Implement QueryArray and PostArray methods
|
||||
- [NEW] Refactor GetQuery and GetPostForm
|
||||
- [NEW] Add contribution guide
|
||||
- [NEW] Implement QueryArray and PostArray methods
|
||||
- [NEW] Refactor GetQuery and GetPostForm
|
||||
- [NEW] Add contribution guide
|
||||
- [FIX] Corrected typos in README
|
||||
- [FIX] Removed additional Iota
|
||||
- [FIX] Changed imports to gopkg instead of github in README (#733)
|
||||
- [FIX] Removed additional Iota
|
||||
- [FIX] Changed imports to gopkg instead of github in README (#733)
|
||||
- [FIX] Logger: skip ANSI color commands if output is not a tty
|
||||
|
||||
### Gin 1.0rc2 (...)
|
||||
## Gin 1.0rc2 (...)
|
||||
|
||||
- [PERFORMANCE] Fast path for writing Content-Type.
|
||||
- [PERFORMANCE] Much faster 404 routing
|
||||
@@ -188,7 +291,7 @@
|
||||
- [FIX] MIT license in every file
|
||||
|
||||
|
||||
### Gin 1.0rc1 (May 22, 2015)
|
||||
## Gin 1.0rc1 (May 22, 2015)
|
||||
|
||||
- [PERFORMANCE] Zero allocation router
|
||||
- [PERFORMANCE] Faster JSON, XML and text rendering
|
||||
@@ -196,7 +299,7 @@
|
||||
- [PERFORMANCE] Misc code optimizations. Inlining, tail call optimizations
|
||||
- [NEW] Built-in support for golang.org/x/net/context
|
||||
- [NEW] Any(path, handler). Create a route that matches any path
|
||||
- [NEW] Refactored rendering pipeline (faster and static typeded)
|
||||
- [NEW] Refactored rendering pipeline (faster and static typed)
|
||||
- [NEW] Refactored errors API
|
||||
- [NEW] IndentedJSON() prints pretty JSON
|
||||
- [NEW] Added gin.DefaultWriter
|
||||
@@ -232,7 +335,7 @@
|
||||
- [FIX] Better support for Google App Engine (using log instead of fmt)
|
||||
|
||||
|
||||
### Gin 0.6 (Mar 9, 2015)
|
||||
## Gin 0.6 (Mar 9, 2015)
|
||||
|
||||
- [NEW] Support multipart/form-data
|
||||
- [NEW] NoMethod handler
|
||||
@@ -242,14 +345,14 @@
|
||||
- [FIX] Improve color logger
|
||||
|
||||
|
||||
### Gin 0.5 (Feb 7, 2015)
|
||||
## Gin 0.5 (Feb 7, 2015)
|
||||
|
||||
- [NEW] Content Negotiation
|
||||
- [FIX] Solved security bug that allow a client to spoof ip
|
||||
- [FIX] Fix unexported/ignored fields in binding
|
||||
|
||||
|
||||
### Gin 0.4 (Aug 21, 2014)
|
||||
## Gin 0.4 (Aug 21, 2014)
|
||||
|
||||
- [NEW] Development mode
|
||||
- [NEW] Unit tests
|
||||
@@ -258,7 +361,7 @@
|
||||
- [FIX] Improved documentation for model binding
|
||||
|
||||
|
||||
### Gin 0.3 (Jul 18, 2014)
|
||||
## Gin 0.3 (Jul 18, 2014)
|
||||
|
||||
- [PERFORMANCE] Normal log and error log are printed in the same call.
|
||||
- [PERFORMANCE] Improve performance of NoRouter()
|
||||
@@ -276,7 +379,7 @@
|
||||
- [FIX] Check application/x-www-form-urlencoded when parsing form
|
||||
|
||||
|
||||
### Gin 0.2b (Jul 08, 2014)
|
||||
## Gin 0.2b (Jul 08, 2014)
|
||||
- [PERFORMANCE] Using sync.Pool to allocatio/gc overhead
|
||||
- [NEW] Travis CI integration
|
||||
- [NEW] Completely new logger
|
||||
@@ -295,7 +398,7 @@
|
||||
- [FIX] Recovery() middleware only prints panics
|
||||
- [FIX] Context.Get() does not panic anymore. Use MustGet() instead.
|
||||
- [FIX] Multiple http.WriteHeader() in NotFound handlers
|
||||
- [FIX] Engine.Run() panics if http server can't be setted up
|
||||
- [FIX] Engine.Run() panics if http server can't be set up
|
||||
- [FIX] Crash when route path doesn't start with '/'
|
||||
- [FIX] Do not update header when status code is negative
|
||||
- [FIX] Setting response headers before calling WriteHeader in context.String()
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
## Contributing
|
||||
## Contributing
|
||||
|
||||
- With issues:
|
||||
- Use the search tool before opening a new issue.
|
||||
@@ -8,6 +8,6 @@
|
||||
- With pull requests:
|
||||
- Open your pull request against `master`
|
||||
- Your pull request should have no more than two commits, if not you should squash them.
|
||||
- It should pass all tests in the available continuous integrations systems such as TravisCI.
|
||||
- It should pass all tests in the available continuous integration systems such as TravisCI.
|
||||
- You should add/modify tests to cover your proposed code changes.
|
||||
- If your pull request contains a new feature, please document it on the README.
|
||||
|
||||
+5
-14
@@ -1,20 +1,16 @@
|
||||
GO ?= go
|
||||
GOFMT ?= gofmt "-s"
|
||||
PACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/)
|
||||
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /vendor/ | grep -v /examples/)
|
||||
GOFILES := $(shell find . -name "*.go" -type f -not -path "./vendor/*")
|
||||
PACKAGES ?= $(shell $(GO) list ./...)
|
||||
VETPACKAGES ?= $(shell $(GO) list ./... | grep -v /examples/)
|
||||
GOFILES := $(shell find . -name "*.go")
|
||||
TESTFOLDER := $(shell $(GO) list ./... | grep -E 'gin$$|binding$$|render$$' | grep -v examples)
|
||||
|
||||
all: install
|
||||
|
||||
install: deps
|
||||
govendor sync
|
||||
TESTTAGS ?= ""
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
echo "mode: count" > coverage.out
|
||||
for d in $(TESTFOLDER); do \
|
||||
$(GO) test -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \
|
||||
$(GO) test -tags $(TESTTAGS) -v -covermode=count -coverprofile=profile.out $$d > tmp.out; \
|
||||
cat tmp.out; \
|
||||
if grep -q "^--- FAIL" tmp.out; then \
|
||||
rm tmp.out; \
|
||||
@@ -48,11 +44,6 @@ fmt-check:
|
||||
vet:
|
||||
$(GO) vet $(VETPACKAGES)
|
||||
|
||||
deps:
|
||||
@hash govendor > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
$(GO) get -u github.com/kardianos/govendor; \
|
||||
fi
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
|
||||
+220
-139
@@ -5,36 +5,41 @@
|
||||
[](https://travis-ci.org/gin-gonic/gin)
|
||||
[](https://codecov.io/gh/gin-gonic/gin)
|
||||
[](https://goreportcard.com/report/github.com/gin-gonic/gin)
|
||||
[](https://godoc.org/github.com/gin-gonic/gin)
|
||||
[](https://pkg.go.dev/github.com/gin-gonic/gin?tab=doc)
|
||||
[](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
|
||||
[](https://www.codetriage.com/gin-gonic/gin)
|
||||
[](https://github.com/gin-gonic/gin/releases)
|
||||
[](https://www.tickgit.com/browse?repo=github.com/gin-gonic/gin)
|
||||
|
||||
Gin is a web framework written in Go (Golang). It features a martini-like API with much better performance, up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||
Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||
|
||||
|
||||
## Contents
|
||||
|
||||
- [Installation](#installation)
|
||||
- [Prerequisite](#prerequisite)
|
||||
- [Quick start](#quick-start)
|
||||
- [Benchmarks](#benchmarks)
|
||||
- [Gin v1.stable](#gin-v1-stable)
|
||||
- [Build with jsoniter](#build-with-jsoniter)
|
||||
- [API Examples](#api-examples)
|
||||
- [Using GET,POST,PUT,PATCH,DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
|
||||
- [Gin Web Framework](#gin-web-framework)
|
||||
- [Contents](#contents)
|
||||
- [Installation](#installation)
|
||||
- [Quick start](#quick-start)
|
||||
- [Benchmarks](#benchmarks)
|
||||
- [Gin v1. stable](#gin-v1-stable)
|
||||
- [Build with jsoniter](#build-with-jsoniter)
|
||||
- [API Examples](#api-examples)
|
||||
- [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
|
||||
- [Parameters in path](#parameters-in-path)
|
||||
- [Querystring parameters](#querystring-parameters)
|
||||
- [Multipart/Urlencoded Form](#multiparturlencoded-form)
|
||||
- [Another example: query + post form](#another-example-query--post-form)
|
||||
- [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
|
||||
- [Upload files](#upload-files)
|
||||
- [Single file](#single-file)
|
||||
- [Multiple files](#multiple-files)
|
||||
- [Grouping routes](#grouping-routes)
|
||||
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
|
||||
- [Using middleware](#using-middleware)
|
||||
- [How to write log file](#how-to-write-log-file)
|
||||
- [Custom Log Format](#custom-log-format)
|
||||
- [Controlling Log output coloring](#controlling-log-output-coloring)
|
||||
- [Model binding and validation](#model-binding-and-validation)
|
||||
- [Custom Validators](#custom-validators)
|
||||
- [Only Bind Query String](#only-bind-query-string)
|
||||
@@ -44,10 +49,17 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
||||
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
||||
- [XML, JSON, YAML and ProtoBuf rendering](#xml-json-yaml-and-protobuf-rendering)
|
||||
- [JSONP rendering](#jsonp)
|
||||
- [SecureJSON](#securejson)
|
||||
- [JSONP](#jsonp)
|
||||
- [AsciiJSON](#asciijson)
|
||||
- [PureJSON](#purejson)
|
||||
- [Serving static files](#serving-static-files)
|
||||
- [Serving data from file](#serving-data-from-file)
|
||||
- [Serving data from reader](#serving-data-from-reader)
|
||||
- [HTML rendering](#html-rendering)
|
||||
- [Custom Template renderer](#custom-template-renderer)
|
||||
- [Custom Delimiters](#custom-delimiters)
|
||||
- [Custom Template Funcs](#custom-template-funcs)
|
||||
- [Multitemplate](#multitemplate)
|
||||
- [Redirects](#redirects)
|
||||
- [Custom Middleware](#custom-middleware)
|
||||
@@ -56,21 +68,23 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi
|
||||
- [Custom HTTP configuration](#custom-http-configuration)
|
||||
- [Support Let's Encrypt](#support-lets-encrypt)
|
||||
- [Run multiple service using Gin](#run-multiple-service-using-gin)
|
||||
- [Graceful restart or stop](#graceful-restart-or-stop)
|
||||
- [Graceful shutdown or restart](#graceful-shutdown-or-restart)
|
||||
- [Third-party packages](#third-party-packages)
|
||||
- [Manually](#manually)
|
||||
- [Build a single binary with templates](#build-a-single-binary-with-templates)
|
||||
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
|
||||
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
|
||||
- [http2 server push](#http2-server-push)
|
||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
||||
- [Testing](#testing)
|
||||
- [Users](#users)
|
||||
- [Testing](#testing)
|
||||
- [Users](#users)
|
||||
|
||||
## Installation
|
||||
|
||||
To install Gin package, you need to install Go and set your Go workspace first.
|
||||
|
||||
1. The first need [Go](https://golang.org/) installed (**version 1.10+ is required**), then you can use the below Go command to install Gin.
|
||||
1. The first need [Go](https://golang.org/) installed (**version 1.12+ is required**), then you can use the below Go command to install Gin.
|
||||
|
||||
```sh
|
||||
$ go get -u github.com/gin-gonic/gin
|
||||
@@ -88,46 +102,8 @@ import "github.com/gin-gonic/gin"
|
||||
import "net/http"
|
||||
```
|
||||
|
||||
### Use a vendor tool like [Govendor](https://github.com/kardianos/govendor)
|
||||
|
||||
1. `go get` govendor
|
||||
|
||||
```sh
|
||||
$ go get github.com/kardianos/govendor
|
||||
```
|
||||
2. Create your project folder and `cd` inside
|
||||
|
||||
```sh
|
||||
$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
|
||||
```
|
||||
|
||||
If you are on a Mac and you're installing Go 1.8 (released: Feb 2017) or later, GOPATH is automatically determined by the Go toolchain for you. It defaults to $HOME/go on macOS so you can create your project like this
|
||||
|
||||
```sh
|
||||
$ mkdir -p $HOME/go/src/github.com/myusername/project && cd "$_"
|
||||
```
|
||||
|
||||
3. Vendor init your project and add gin
|
||||
|
||||
```sh
|
||||
$ govendor init
|
||||
$ govendor fetch github.com/gin-gonic/gin@v1.3
|
||||
```
|
||||
|
||||
4. Copy a starting template inside your project
|
||||
|
||||
```sh
|
||||
$ curl https://raw.githubusercontent.com/gin-gonic/examples/master/basic/main.go > main.go
|
||||
```
|
||||
|
||||
5. Run your project
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
```
|
||||
|
||||
## Quick start
|
||||
|
||||
|
||||
```sh
|
||||
# assume the following codes in example.go file
|
||||
$ cat example.go
|
||||
@@ -160,35 +136,38 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
|
||||
|
||||
[See all benchmarks](/BENCHMARKS.md)
|
||||
|
||||
Benchmark name | (1) | (2) | (3) | (4)
|
||||
--------------------------------------------|-----------:|------------:|-----------:|---------:
|
||||
**BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0**
|
||||
BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167
|
||||
BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943
|
||||
BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812
|
||||
BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453
|
||||
BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167
|
||||
BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203
|
||||
BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686
|
||||
BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334
|
||||
BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712
|
||||
BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737
|
||||
BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519
|
||||
BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272
|
||||
BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167
|
||||
BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671
|
||||
BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843
|
||||
BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0
|
||||
BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000
|
||||
BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325
|
||||
BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435
|
||||
BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609
|
||||
BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979
|
||||
BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167
|
||||
BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618
|
||||
BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374
|
||||
BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848
|
||||
BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609
|
||||
| Benchmark name | (1) | (2) | (3) | (4) |
|
||||
| ------------------------------ | ---------:| ---------------:| ------------:| ---------------:|
|
||||
| BenchmarkGin_GithubAll | **43550** | **27364 ns/op** | **0 B/op** | **0 allocs/op** |
|
||||
| BenchmarkAce_GithubAll | 40543 | 29670 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkAero_GithubAll | 57632 | 20648 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkBear_GithubAll | 9234 | 216179 ns/op | 86448 B/op | 943 allocs/op |
|
||||
| BenchmarkBeego_GithubAll | 7407 | 243496 ns/op | 71456 B/op | 609 allocs/op |
|
||||
| BenchmarkBone_GithubAll | 420 | 2922835 ns/op | 720160 B/op | 8620 allocs/op |
|
||||
| BenchmarkChi_GithubAll | 7620 | 238331 ns/op | 87696 B/op | 609 allocs/op |
|
||||
| BenchmarkDenco_GithubAll | 18355 | 64494 ns/op | 20224 B/op | 167 allocs/op |
|
||||
| BenchmarkEcho_GithubAll | 31251 | 38479 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkGocraftWeb_GithubAll | 4117 | 300062 ns/op | 131656 B/op | 1686 allocs/op |
|
||||
| BenchmarkGoji_GithubAll | 3274 | 416158 ns/op | 56112 B/op | 334 allocs/op |
|
||||
| BenchmarkGojiv2_GithubAll | 1402 | 870518 ns/op | 352720 B/op | 4321 allocs/op |
|
||||
| BenchmarkGoJsonRest_GithubAll | 2976 | 401507 ns/op | 134371 B/op | 2737 allocs/op |
|
||||
| BenchmarkGoRestful_GithubAll | 410 | 2913158 ns/op | 910144 B/op | 2938 allocs/op |
|
||||
| BenchmarkGorillaMux_GithubAll | 346 | 3384987 ns/op | 251650 B/op | 1994 allocs/op |
|
||||
| BenchmarkGowwwRouter_GithubAll | 10000 | 143025 ns/op | 72144 B/op | 501 allocs/op |
|
||||
| BenchmarkHttpRouter_GithubAll | 55938 | 21360 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkHttpTreeMux_GithubAll | 10000 | 153944 ns/op | 65856 B/op | 671 allocs/op |
|
||||
| BenchmarkKocha_GithubAll | 10000 | 106315 ns/op | 23304 B/op | 843 allocs/op |
|
||||
| BenchmarkLARS_GithubAll | 47779 | 25084 ns/op | 0 B/op | 0 allocs/op |
|
||||
| BenchmarkMacaron_GithubAll | 3266 | 371907 ns/op | 149409 B/op | 1624 allocs/op |
|
||||
| BenchmarkMartini_GithubAll | 331 | 3444706 ns/op | 226551 B/op | 2325 allocs/op |
|
||||
| BenchmarkPat_GithubAll | 273 | 4381818 ns/op | 1483152 B/op | 26963 allocs/op |
|
||||
| BenchmarkPossum_GithubAll | 10000 | 164367 ns/op | 84448 B/op | 609 allocs/op |
|
||||
| BenchmarkR2router_GithubAll | 10000 | 160220 ns/op | 77328 B/op | 979 allocs/op |
|
||||
| BenchmarkRivet_GithubAll | 14625 | 82453 ns/op | 16272 B/op | 167 allocs/op |
|
||||
| BenchmarkTango_GithubAll | 6255 | 279611 ns/op | 63826 B/op | 1618 allocs/op |
|
||||
| BenchmarkTigerTonic_GithubAll | 2008 | 687874 ns/op | 193856 B/op | 4474 allocs/op |
|
||||
| BenchmarkTraffic_GithubAll | 355 | 3478508 ns/op | 820744 B/op | 14114 allocs/op |
|
||||
| BenchmarkVulcan_GithubAll | 6885 | 193333 ns/op | 19894 B/op | 609 allocs/op |
|
||||
|
||||
- (1): Total Repetitions achieved in constant time, higher means more confident result
|
||||
- (2): Single Repetition Duration (ns/op), lower is better
|
||||
@@ -199,8 +178,8 @@ BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894
|
||||
|
||||
- [x] Zero allocation router.
|
||||
- [x] Still the fastest http router and framework. From routing to writing.
|
||||
- [x] Complete suite of unit tests
|
||||
- [x] Battle tested
|
||||
- [x] Complete suite of unit tests.
|
||||
- [x] Battle tested.
|
||||
- [x] API frozen, new releases will not break your code.
|
||||
|
||||
## Build with [jsoniter](https://github.com/json-iterator/go)
|
||||
@@ -264,6 +243,13 @@ func main() {
|
||||
c.FullPath() == "/user/:name/*action" // true
|
||||
})
|
||||
|
||||
// This handler will add a new router for /user/groups.
|
||||
// Exact routes are resolved before param routes, regardless of the order they were defined.
|
||||
// Routes starting with /user/groups are never interpreted as /user/:name/... routes
|
||||
router.GET("/user/groups", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "The available groups are [...]", name)
|
||||
})
|
||||
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
@@ -361,7 +347,7 @@ func main() {
|
||||
```
|
||||
|
||||
```
|
||||
ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
|
||||
ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]
|
||||
```
|
||||
|
||||
### Upload files
|
||||
@@ -378,14 +364,14 @@ References issue [#774](https://github.com/gin-gonic/gin/issues/774) and detail
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
||||
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||
router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||
router.POST("/upload", func(c *gin.Context) {
|
||||
// single file
|
||||
file, _ := c.FormFile("file")
|
||||
log.Println(file.Filename)
|
||||
|
||||
// Upload the file to specific dst.
|
||||
// c.SaveUploadedFile(file, dst)
|
||||
c.SaveUploadedFile(file, dst)
|
||||
|
||||
c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
|
||||
})
|
||||
@@ -409,7 +395,7 @@ See the detail [example code](https://github.com/gin-gonic/examples/tree/master/
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
// Set a lower memory limit for multipart forms (default is 32 MiB)
|
||||
// router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||
router.MaxMultipartMemory = 8 << 20 // 8 MiB
|
||||
router.POST("/upload", func(c *gin.Context) {
|
||||
// Multipart form
|
||||
form, _ := c.MultipartForm()
|
||||
@@ -419,7 +405,7 @@ func main() {
|
||||
log.Println(file.Filename)
|
||||
|
||||
// Upload the file to specific dst.
|
||||
// c.SaveUploadedFile(file, dst)
|
||||
c.SaveUploadedFile(file, dst)
|
||||
}
|
||||
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
|
||||
})
|
||||
@@ -517,6 +503,39 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Recovery behavior
|
||||
```go
|
||||
func main() {
|
||||
// Creates a router without any middleware by default
|
||||
r := gin.New()
|
||||
|
||||
// Global middleware
|
||||
// Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
|
||||
// By default gin.DefaultWriter = os.Stdout
|
||||
r.Use(gin.Logger())
|
||||
|
||||
// Recovery middleware recovers from any panics and writes a 500 if there was one.
|
||||
r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
|
||||
if err, ok := recovered.(string); ok {
|
||||
c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
|
||||
}
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}))
|
||||
|
||||
r.GET("/panic", func(c *gin.Context) {
|
||||
// panic with a string -- the custom middleware could save this to a database or report it to the user
|
||||
panic("foo")
|
||||
})
|
||||
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "ohai")
|
||||
})
|
||||
|
||||
// Listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
### How to write log file
|
||||
```go
|
||||
func main() {
|
||||
@@ -576,44 +595,44 @@ func main() {
|
||||
::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "
|
||||
```
|
||||
|
||||
### Controlling Log output coloring
|
||||
### Controlling Log output coloring
|
||||
|
||||
By default, logs output on console should be colorized depending on the detected TTY.
|
||||
|
||||
Never colorize logs:
|
||||
Never colorize logs:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// Disable log's color
|
||||
gin.DisableConsoleColor()
|
||||
|
||||
|
||||
// Creates a gin router with default middleware:
|
||||
// logger and recovery (crash-free) middleware
|
||||
router := gin.Default()
|
||||
|
||||
|
||||
router.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
})
|
||||
|
||||
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
Always colorize logs:
|
||||
Always colorize logs:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// Force log's color
|
||||
gin.ForceConsoleColor()
|
||||
|
||||
|
||||
// Creates a gin router with default middleware:
|
||||
// logger and recovery (crash-free) middleware
|
||||
router := gin.Default()
|
||||
|
||||
|
||||
router.GET("/ping", func(c *gin.Context) {
|
||||
c.String(200, "pong")
|
||||
})
|
||||
|
||||
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
@@ -622,7 +641,7 @@ func main() {
|
||||
|
||||
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML and standard form values (foo=bar&boo=baz).
|
||||
|
||||
Gin uses [**go-playground/validator.v8**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags).
|
||||
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://godoc.org/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
|
||||
|
||||
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
|
||||
|
||||
@@ -655,12 +674,12 @@ func main() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if json.User != "manu" || json.Password != "123" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
})
|
||||
|
||||
@@ -676,12 +695,12 @@ func main() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if xml.User != "manu" || xml.Password != "123" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
})
|
||||
|
||||
@@ -693,12 +712,12 @@ func main() {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if form.User != "manu" || form.Password != "123" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
|
||||
})
|
||||
|
||||
@@ -742,12 +761,11 @@ package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"gopkg.in/go-playground/validator.v8"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// Booking contains binded and validated data.
|
||||
@@ -756,11 +774,9 @@ type Booking struct {
|
||||
CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
|
||||
}
|
||||
|
||||
func bookableDate(
|
||||
v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,
|
||||
field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,
|
||||
) bool {
|
||||
if date, ok := field.Interface().(time.Time); ok {
|
||||
var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
|
||||
date, ok := fl.Field().Interface().(time.Time)
|
||||
if ok {
|
||||
today := time.Now()
|
||||
if today.After(date) {
|
||||
return false
|
||||
@@ -791,11 +807,14 @@ func getBookable(c *gin.Context) {
|
||||
```
|
||||
|
||||
```console
|
||||
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"
|
||||
$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
|
||||
{"message":"Booking dates are valid!"}
|
||||
|
||||
$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"
|
||||
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
|
||||
$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
|
||||
{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
|
||||
|
||||
$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
|
||||
{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%
|
||||
```
|
||||
|
||||
[Struct level validations](https://github.com/go-playground/validator/releases/tag/v8.7) can also be registered this way.
|
||||
@@ -1133,7 +1152,7 @@ func main() {
|
||||
data := gin.H{
|
||||
"foo": "bar",
|
||||
}
|
||||
|
||||
|
||||
//callback is x
|
||||
// Will output : x({\"foo\":\"bar\"})
|
||||
c.JSONP(http.StatusOK, data)
|
||||
@@ -1178,21 +1197,21 @@ This feature is unavailable in Go 1.6 and lower.
|
||||
```go
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
|
||||
|
||||
// Serves unicode entities
|
||||
r.GET("/json", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{
|
||||
"html": "<b>Hello, world!</b>",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// Serves literal characters
|
||||
r.GET("/purejson", func(c *gin.Context) {
|
||||
c.PureJSON(200, gin.H{
|
||||
"html": "<b>Hello, world!</b>",
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
// listen and serve on 0.0.0.0:8080
|
||||
r.Run(":8080")
|
||||
}
|
||||
@@ -1212,6 +1231,24 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Serving data from file
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
|
||||
router.GET("/local/file", func(c *gin.Context) {
|
||||
c.File("local/file.go")
|
||||
})
|
||||
|
||||
var fs http.FileSystem = // ...
|
||||
router.GET("/fs/file", func(c *gin.Context) {
|
||||
c.FileFromFS("fs/file.go", fs)
|
||||
})
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Serving data from reader
|
||||
|
||||
```go
|
||||
@@ -1225,6 +1262,7 @@ func main() {
|
||||
}
|
||||
|
||||
reader := response.Body
|
||||
defer reader.Close()
|
||||
contentLength := response.ContentLength
|
||||
contentType := response.Header.Get("Content-Type")
|
||||
|
||||
@@ -1400,6 +1438,12 @@ r.GET("/test", func(c *gin.Context) {
|
||||
})
|
||||
```
|
||||
|
||||
Issuing a HTTP redirect from POST. Refer to issue: [#444](https://github.com/gin-gonic/gin/issues/444)
|
||||
```go
|
||||
r.POST("/test", func(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, "/foo")
|
||||
})
|
||||
```
|
||||
|
||||
Issuing a Router redirect, use `HandleContext` like below.
|
||||
|
||||
@@ -1699,12 +1743,13 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Graceful restart or stop
|
||||
### Graceful shutdown or restart
|
||||
|
||||
Do you want to graceful restart or stop your web server?
|
||||
There are some ways this can be done.
|
||||
There are a few approaches you can use to perform a graceful shutdown or restart. You can make use of third-party packages specifically built for that, or you can manually do the same with the functions and methods from the built-in packages.
|
||||
|
||||
We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.
|
||||
#### Third-party packages
|
||||
|
||||
We can use [fvbock/endless](https://github.com/fvbock/endless) to replace the default `ListenAndServe`. Refer to issue [#296](https://github.com/gin-gonic/gin/issues/296) for more details.
|
||||
|
||||
```go
|
||||
router := gin.Default()
|
||||
@@ -1713,13 +1758,15 @@ router.GET("/", handler)
|
||||
endless.ListenAndServe(":4242", router)
|
||||
```
|
||||
|
||||
An alternative to endless:
|
||||
Alternatives:
|
||||
|
||||
* [manners](https://github.com/braintree/manners): A polite Go HTTP server that shuts down gracefully.
|
||||
* [graceful](https://github.com/tylerb/graceful): Graceful is a Go package enabling graceful shutdown of an http.Handler server.
|
||||
* [grace](https://github.com/facebookgo/grace): Graceful restart & zero downtime deploy for Go servers.
|
||||
|
||||
If you are using Go 1.8, you may not need to use this library! Consider using http.Server's built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. See the full [graceful-shutdown](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown) example with gin.
|
||||
#### Manually
|
||||
|
||||
In case you are using Go 1.8 or a later version, you may not need to use those libraries. Consider using `http.Server`'s built-in [Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown) method for graceful shutdowns. The example below describes its usage, and we've got more examples using gin [here](https://github.com/gin-gonic/examples/tree/master/graceful-shutdown).
|
||||
|
||||
```go
|
||||
// +build go1.8
|
||||
@@ -1750,10 +1797,11 @@ func main() {
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
// Initializing the server in a goroutine so that
|
||||
// it won't block the graceful shutdown handling below
|
||||
go func() {
|
||||
// service connections
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
log.Fatalf("listen: %s\n", err)
|
||||
if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
|
||||
log.Printf("listen: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -1765,18 +1813,17 @@ func main() {
|
||||
// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
|
||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||
<-quit
|
||||
log.Println("Shutdown Server ...")
|
||||
log.Println("Shutting down server...")
|
||||
|
||||
// The context is used to inform the server it has 5 seconds to finish
|
||||
// the request it is currently handling
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Fatal("Server Shutdown:", err)
|
||||
}
|
||||
// catching ctx.Done(). timeout of 5 seconds.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Println("timeout of 5 seconds.")
|
||||
log.Fatal("Server forced to shutdown:", err)
|
||||
}
|
||||
|
||||
log.Println("Server exiting")
|
||||
}
|
||||
```
|
||||
@@ -2077,6 +2124,39 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Don't trust all proxies
|
||||
|
||||
Gin lets you specify which headers to hold the real client IP (if any),
|
||||
as well as specifying which proxies (or direct clients) you trust to
|
||||
specify one of these headers.
|
||||
|
||||
The `TrustedProxies` slice on your `gin.Engine` specifes network addresses or
|
||||
network CIDRs from where clients which their request headers related to client
|
||||
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
||||
IPv6 CIDRs.
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
router := gin.Default()
|
||||
router.TrustedProxies = []string{"192.168.1.2"}
|
||||
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
// If the client is 192.168.1.2, use the X-Forwarded-For
|
||||
// header to deduce the original client IP from the trust-
|
||||
// worthy parts of that header.
|
||||
// Otherwise, simply return the direct client IP
|
||||
fmt.Printf("ClientIP: %s\n", c.ClientIP())
|
||||
})
|
||||
router.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
@@ -2134,3 +2214,4 @@ Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framewor
|
||||
* [krakend](https://github.com/devopsfaith/krakend): Ultra performant API Gateway with middlewares.
|
||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||
|
||||
+8
-4
@@ -5,9 +5,12 @@
|
||||
package gin
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
)
|
||||
|
||||
// AuthUserKey is the cookie name for user credential in basic auth.
|
||||
@@ -28,7 +31,7 @@ func (a authPairs) searchCredential(authValue string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
for _, pair := range a {
|
||||
if pair.value == authValue {
|
||||
if subtle.ConstantTimeCompare([]byte(pair.value), []byte(authValue)) == 1 {
|
||||
return pair.user, true
|
||||
}
|
||||
}
|
||||
@@ -68,8 +71,9 @@ func BasicAuth(accounts Accounts) HandlerFunc {
|
||||
}
|
||||
|
||||
func processAccounts(accounts Accounts) authPairs {
|
||||
assert1(len(accounts) > 0, "Empty list of authorized credentials")
|
||||
pairs := make(authPairs, 0, len(accounts))
|
||||
length := len(accounts)
|
||||
assert1(length > 0, "Empty list of authorized credentials")
|
||||
pairs := make(authPairs, 0, length)
|
||||
for user, password := range accounts {
|
||||
assert1(user != "", "User can not be empty")
|
||||
value := authorizationHeader(user, password)
|
||||
@@ -83,5 +87,5 @@ func processAccounts(accounts Accounts) authPairs {
|
||||
|
||||
func authorizationHeader(user, password string) string {
|
||||
base := user + ":" + password
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(base))
|
||||
return "Basic " + base64.StdEncoding.EncodeToString(bytesconv.StringToBytes(base))
|
||||
}
|
||||
|
||||
+6
-2
@@ -2,6 +2,9 @@
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
import "net/http"
|
||||
@@ -49,7 +52,8 @@ type BindingUri interface {
|
||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||
type StructValidator interface {
|
||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
||||
// If the received type is a slice|array, the validation should be performed travel on every element.
|
||||
// If the received type is not a struct or slice|array, any validation should be skipped and nil must be returned.
|
||||
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||
// Otherwise nil must be returned.
|
||||
@@ -84,7 +88,7 @@ var (
|
||||
// Default returns the appropriate Binding instance based on the HTTP method
|
||||
// and the content type.
|
||||
func Default(method, contentType string) Binding {
|
||||
if method == "GET" {
|
||||
if method == http.MethodGet {
|
||||
return Form
|
||||
}
|
||||
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
// Copyright 2020 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build nomsgpack
|
||||
// +build nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Content-Type MIME of the most common data formats.
|
||||
const (
|
||||
MIMEJSON = "application/json"
|
||||
MIMEHTML = "text/html"
|
||||
MIMEXML = "application/xml"
|
||||
MIMEXML2 = "text/xml"
|
||||
MIMEPlain = "text/plain"
|
||||
MIMEPOSTForm = "application/x-www-form-urlencoded"
|
||||
MIMEMultipartPOSTForm = "multipart/form-data"
|
||||
MIMEPROTOBUF = "application/x-protobuf"
|
||||
MIMEYAML = "application/x-yaml"
|
||||
)
|
||||
|
||||
// Binding describes the interface which needs to be implemented for binding the
|
||||
// data present in the request such as JSON request body, query parameters or
|
||||
// the form POST.
|
||||
type Binding interface {
|
||||
Name() string
|
||||
Bind(*http.Request, interface{}) error
|
||||
}
|
||||
|
||||
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
|
||||
// but it reads the body from supplied bytes instead of req.Body.
|
||||
type BindingBody interface {
|
||||
Binding
|
||||
BindBody([]byte, interface{}) error
|
||||
}
|
||||
|
||||
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
|
||||
// but it read the Params.
|
||||
type BindingUri interface {
|
||||
Name() string
|
||||
BindUri(map[string][]string, interface{}) error
|
||||
}
|
||||
|
||||
// StructValidator is the minimal interface which needs to be implemented in
|
||||
// order for it to be used as the validator engine for ensuring the correctness
|
||||
// of the request. Gin provides a default implementation for this using
|
||||
// https://github.com/go-playground/validator/tree/v8.18.2.
|
||||
type StructValidator interface {
|
||||
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
|
||||
// If the received type is not a struct, any validation should be skipped and nil must be returned.
|
||||
// If the received type is a struct or pointer to a struct, the validation should be performed.
|
||||
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
|
||||
// Otherwise nil must be returned.
|
||||
ValidateStruct(interface{}) error
|
||||
|
||||
// Engine returns the underlying validator engine which powers the
|
||||
// StructValidator implementation.
|
||||
Engine() interface{}
|
||||
}
|
||||
|
||||
// Validator is the default validator which implements the StructValidator
|
||||
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
|
||||
// under the hood.
|
||||
var Validator StructValidator = &defaultValidator{}
|
||||
|
||||
// These implement the Binding interface and can be used to bind the data
|
||||
// present in the request to struct instances.
|
||||
var (
|
||||
JSON = jsonBinding{}
|
||||
XML = xmlBinding{}
|
||||
Form = formBinding{}
|
||||
Query = queryBinding{}
|
||||
FormPost = formPostBinding{}
|
||||
FormMultipart = formMultipartBinding{}
|
||||
ProtoBuf = protobufBinding{}
|
||||
YAML = yamlBinding{}
|
||||
Uri = uriBinding{}
|
||||
Header = headerBinding{}
|
||||
)
|
||||
|
||||
// Default returns the appropriate Binding instance based on the HTTP method
|
||||
// and the content type.
|
||||
func Default(method, contentType string) Binding {
|
||||
if method == "GET" {
|
||||
return Form
|
||||
}
|
||||
|
||||
switch contentType {
|
||||
case MIMEJSON:
|
||||
return JSON
|
||||
case MIMEXML, MIMEXML2:
|
||||
return XML
|
||||
case MIMEPROTOBUF:
|
||||
return ProtoBuf
|
||||
case MIMEYAML:
|
||||
return YAML
|
||||
case MIMEMultipartPOSTForm:
|
||||
return FormMultipart
|
||||
default: // case MIMEPOSTForm:
|
||||
return Form
|
||||
}
|
||||
}
|
||||
|
||||
func validate(obj interface{}) error {
|
||||
if Validator == nil {
|
||||
return nil
|
||||
}
|
||||
return Validator.ValidateStruct(obj)
|
||||
}
|
||||
+44
-10
@@ -5,10 +5,12 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"gopkg.in/go-playground/validator.v9"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
type defaultValidator struct {
|
||||
@@ -16,22 +18,54 @@ type defaultValidator struct {
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
type sliceValidateError []error
|
||||
|
||||
func (err sliceValidateError) Error() string {
|
||||
var errMsgs []string
|
||||
for i, e := range err {
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("[%d]: %s", i, e.Error()))
|
||||
}
|
||||
return strings.Join(errMsgs, "\n")
|
||||
}
|
||||
|
||||
var _ StructValidator = &defaultValidator{}
|
||||
|
||||
// ValidateStruct receives any kind of type, but only performed struct or pointer to struct type.
|
||||
func (v *defaultValidator) ValidateStruct(obj interface{}) error {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
value := reflect.ValueOf(obj)
|
||||
valueType := value.Kind()
|
||||
if valueType == reflect.Ptr {
|
||||
valueType = value.Elem().Kind()
|
||||
}
|
||||
if valueType == reflect.Struct {
|
||||
v.lazyinit()
|
||||
if err := v.validate.Struct(obj); err != nil {
|
||||
return err
|
||||
switch value.Kind() {
|
||||
case reflect.Ptr:
|
||||
return v.ValidateStruct(value.Elem().Interface())
|
||||
case reflect.Struct:
|
||||
return v.validateStruct(obj)
|
||||
case reflect.Slice, reflect.Array:
|
||||
count := value.Len()
|
||||
validateRet := make(sliceValidateError, 0)
|
||||
for i := 0; i < count; i++ {
|
||||
if err := v.ValidateStruct(value.Index(i).Interface()); err != nil {
|
||||
validateRet = append(validateRet, err)
|
||||
}
|
||||
}
|
||||
if len(validateRet) == 0 {
|
||||
return nil
|
||||
}
|
||||
return validateRet
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateStruct receives struct type
|
||||
func (v *defaultValidator) validateStruct(obj interface{}) error {
|
||||
v.lazyinit()
|
||||
return v.validate.Struct(obj)
|
||||
}
|
||||
|
||||
// Engine returns the underlying validator engine which powers the default
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const defaultMemory = 32 * 1024 * 1024
|
||||
const defaultMemory = 32 << 20
|
||||
|
||||
type formBinding struct{}
|
||||
type formPostBinding struct{}
|
||||
|
||||
+45
-3
@@ -12,6 +12,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
)
|
||||
|
||||
@@ -28,6 +29,21 @@ func mapForm(ptr interface{}, form map[string][]string) error {
|
||||
var emptyField = reflect.StructField{}
|
||||
|
||||
func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
|
||||
// Check if ptr is a map
|
||||
ptrVal := reflect.ValueOf(ptr)
|
||||
var pointed interface{}
|
||||
if ptrVal.Kind() == reflect.Ptr {
|
||||
ptrVal = ptrVal.Elem()
|
||||
pointed = ptrVal.Interface()
|
||||
}
|
||||
if ptrVal.Kind() == reflect.Map &&
|
||||
ptrVal.Type().Key().Kind() == reflect.String {
|
||||
if pointed != nil {
|
||||
ptr = pointed
|
||||
}
|
||||
return setFormMap(ptr, form)
|
||||
}
|
||||
|
||||
return mappingByPtr(ptr, formSource(form), tag)
|
||||
}
|
||||
|
||||
@@ -208,9 +224,9 @@ func setWithProperType(val string, value reflect.Value, field reflect.StructFiel
|
||||
case time.Time:
|
||||
return setTimeField(val, field, value)
|
||||
}
|
||||
return json.Unmarshal([]byte(val), value.Addr().Interface())
|
||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||
case reflect.Map:
|
||||
return json.Unmarshal([]byte(val), value.Addr().Interface())
|
||||
return json.Unmarshal(bytesconv.StringToBytes(val), value.Addr().Interface())
|
||||
default:
|
||||
return errUnknownType
|
||||
}
|
||||
@@ -269,7 +285,7 @@ func setTimeField(val string, structField reflect.StructField, value reflect.Val
|
||||
|
||||
switch tf := strings.ToLower(timeFormat); tf {
|
||||
case "unix", "unixnano":
|
||||
tv, err := strconv.ParseInt(val, 10, 0)
|
||||
tv, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -348,3 +364,29 @@ func head(str, sep string) (head string, tail string) {
|
||||
}
|
||||
return str[:idx], str[idx+len(sep):]
|
||||
}
|
||||
|
||||
func setFormMap(ptr interface{}, form map[string][]string) error {
|
||||
el := reflect.TypeOf(ptr).Elem()
|
||||
|
||||
if el.Kind() == reflect.Slice {
|
||||
ptrMap, ok := ptr.(map[string][]string)
|
||||
if !ok {
|
||||
return errors.New("cannot convert to map slices of strings")
|
||||
}
|
||||
for k, v := range form {
|
||||
ptrMap[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
ptrMap, ok := ptr.(map[string]string)
|
||||
if !ok {
|
||||
return errors.New("cannot convert to map of strings")
|
||||
}
|
||||
for k, v := range form {
|
||||
ptrMap[k] = v[len(v)-1] // pick last
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+3
@@ -2,6 +2,9 @@
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
|
||||
+156
-47
@@ -16,6 +16,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/sse"
|
||||
@@ -33,9 +34,11 @@ const (
|
||||
MIMEPOSTForm = binding.MIMEPOSTForm
|
||||
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
|
||||
MIMEYAML = binding.MIMEYAML
|
||||
BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
||||
)
|
||||
|
||||
// BodyBytesKey indicates a default body bytes key.
|
||||
const BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
|
||||
|
||||
const abortIndex int8 = math.MaxInt8 / 2
|
||||
|
||||
// Context is the most important part of gin. It allows us to pass variables between middleware,
|
||||
@@ -51,6 +54,10 @@ type Context struct {
|
||||
fullPath string
|
||||
|
||||
engine *Engine
|
||||
params *Params
|
||||
|
||||
// This mutex protect Keys map
|
||||
mu sync.RWMutex
|
||||
|
||||
// Keys is a key/value pair exclusively for the context of each request.
|
||||
Keys map[string]interface{}
|
||||
@@ -67,6 +74,10 @@ type Context struct {
|
||||
// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
|
||||
// or PUT body parameters.
|
||||
formCache url.Values
|
||||
|
||||
// SameSite allows a server to define a cookie attribute making it impossible for
|
||||
// the browser to send this cookie along with cross-site requests.
|
||||
sameSite http.SameSite
|
||||
}
|
||||
|
||||
/************************************/
|
||||
@@ -78,18 +89,25 @@ func (c *Context) reset() {
|
||||
c.Params = c.Params[0:0]
|
||||
c.handlers = nil
|
||||
c.index = -1
|
||||
|
||||
c.fullPath = ""
|
||||
c.Keys = nil
|
||||
c.Errors = c.Errors[0:0]
|
||||
c.Accepted = nil
|
||||
c.queryCache = nil
|
||||
c.formCache = nil
|
||||
*c.params = (*c.params)[0:0]
|
||||
}
|
||||
|
||||
// Copy returns a copy of the current context that can be safely used outside the request's scope.
|
||||
// This has to be used when the context has to be passed to a goroutine.
|
||||
func (c *Context) Copy() *Context {
|
||||
var cp = *c
|
||||
cp := Context{
|
||||
writermem: c.writermem,
|
||||
Request: c.Request,
|
||||
Params: c.Params,
|
||||
engine: c.engine,
|
||||
}
|
||||
cp.writermem.ResponseWriter = nil
|
||||
cp.Writer = &cp.writermem
|
||||
cp.index = abortIndex
|
||||
@@ -219,16 +237,21 @@ func (c *Context) Error(err error) *Error {
|
||||
// Set is used to store a new key/value pair exclusively for this context.
|
||||
// It also lazy initializes c.Keys if it was not used previously.
|
||||
func (c *Context) Set(key string, value interface{}) {
|
||||
c.mu.Lock()
|
||||
if c.Keys == nil {
|
||||
c.Keys = make(map[string]interface{})
|
||||
}
|
||||
|
||||
c.Keys[key] = value
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// Get returns the value for the given key, ie: (value, true).
|
||||
// If the value does not exists it returns (nil, false)
|
||||
func (c *Context) Get(key string) (value interface{}, exists bool) {
|
||||
c.mu.RLock()
|
||||
value, exists = c.Keys[key]
|
||||
c.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -272,6 +295,22 @@ func (c *Context) GetInt64(key string) (i64 int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetUint returns the value associated with the key as an unsigned integer.
|
||||
func (c *Context) GetUint(key string) (ui uint) {
|
||||
if val, ok := c.Get(key); ok && val != nil {
|
||||
ui, _ = val.(uint)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetUint64 returns the value associated with the key as an unsigned integer.
|
||||
func (c *Context) GetUint64(key string) (ui64 uint64) {
|
||||
if val, ok := c.Get(key); ok && val != nil {
|
||||
ui64, _ = val.(uint64)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetFloat64 returns the value associated with the key as a float64.
|
||||
func (c *Context) GetFloat64(key string) (f64 float64) {
|
||||
if val, ok := c.Get(key); ok && val != nil {
|
||||
@@ -391,16 +430,20 @@ func (c *Context) QueryArray(key string) []string {
|
||||
return values
|
||||
}
|
||||
|
||||
func (c *Context) getQueryCache() {
|
||||
func (c *Context) initQueryCache() {
|
||||
if c.queryCache == nil {
|
||||
c.queryCache = c.Request.URL.Query()
|
||||
if c.Request != nil {
|
||||
c.queryCache = c.Request.URL.Query()
|
||||
} else {
|
||||
c.queryCache = url.Values{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetQueryArray returns a slice of strings for a given query key, plus
|
||||
// a boolean value whether at least one value exists for the given key.
|
||||
func (c *Context) GetQueryArray(key string) ([]string, bool) {
|
||||
c.getQueryCache()
|
||||
c.initQueryCache()
|
||||
if values, ok := c.queryCache[key]; ok && len(values) > 0 {
|
||||
return values, true
|
||||
}
|
||||
@@ -416,7 +459,7 @@ func (c *Context) QueryMap(key string) map[string]string {
|
||||
// GetQueryMap returns a map for a given query key, plus a boolean value
|
||||
// whether at least one value exists for the given key.
|
||||
func (c *Context) GetQueryMap(key string) (map[string]string, bool) {
|
||||
c.getQueryCache()
|
||||
c.initQueryCache()
|
||||
return c.get(c.queryCache, key)
|
||||
}
|
||||
|
||||
@@ -458,7 +501,7 @@ func (c *Context) PostFormArray(key string) []string {
|
||||
return values
|
||||
}
|
||||
|
||||
func (c *Context) getFormCache() {
|
||||
func (c *Context) initFormCache() {
|
||||
if c.formCache == nil {
|
||||
c.formCache = make(url.Values)
|
||||
req := c.Request
|
||||
@@ -474,7 +517,7 @@ func (c *Context) getFormCache() {
|
||||
// GetPostFormArray returns a slice of strings for a given form key, plus
|
||||
// a boolean value whether at least one value exists for the given key.
|
||||
func (c *Context) GetPostFormArray(key string) ([]string, bool) {
|
||||
c.getFormCache()
|
||||
c.initFormCache()
|
||||
if values := c.formCache[key]; len(values) > 0 {
|
||||
return values, true
|
||||
}
|
||||
@@ -490,7 +533,7 @@ func (c *Context) PostFormMap(key string) map[string]string {
|
||||
// GetPostFormMap returns a map for a given form key, plus a boolean value
|
||||
// whether at least one value exists for the given key.
|
||||
func (c *Context) GetPostFormMap(key string) (map[string]string, bool) {
|
||||
c.getFormCache()
|
||||
c.initFormCache()
|
||||
return c.get(c.formCache, key)
|
||||
}
|
||||
|
||||
@@ -682,32 +725,82 @@ func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (e
|
||||
return bb.BindBody(body, obj)
|
||||
}
|
||||
|
||||
// ClientIP implements a best effort algorithm to return the real client IP, it parses
|
||||
// X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy.
|
||||
// Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
|
||||
// ClientIP implements a best effort algorithm to return the real client IP.
|
||||
// It called c.RemoteIP() under the hood, to check if the remote IP is a trusted proxy or not.
|
||||
// If it's it will then try to parse the headers defined in Engine.RemoteIPHeaders (defaulting to [X-Forwarded-For, X-Real-Ip]).
|
||||
// If the headers are nots syntactically valid OR the remote IP does not correspong to a trusted proxy,
|
||||
// the remote IP (coming form Request.RemoteAddr) is returned.
|
||||
func (c *Context) ClientIP() string {
|
||||
if c.engine.ForwardedByClientIP {
|
||||
clientIP := c.requestHeader("X-Forwarded-For")
|
||||
clientIP = strings.TrimSpace(strings.Split(clientIP, ",")[0])
|
||||
if clientIP == "" {
|
||||
clientIP = strings.TrimSpace(c.requestHeader("X-Real-Ip"))
|
||||
}
|
||||
if clientIP != "" {
|
||||
return clientIP
|
||||
}
|
||||
}
|
||||
|
||||
if c.engine.AppEngine {
|
||||
if addr := c.requestHeader("X-Appengine-Remote-Addr"); addr != "" {
|
||||
return addr
|
||||
}
|
||||
}
|
||||
|
||||
if ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr)); err == nil {
|
||||
return ip
|
||||
remoteIP, trusted := c.RemoteIP()
|
||||
if remoteIP == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return ""
|
||||
if trusted && c.engine.ForwardedByClientIP && c.engine.RemoteIPHeaders != nil {
|
||||
for _, headerName := range c.engine.RemoteIPHeaders {
|
||||
ip, valid := validateHeader(c.requestHeader(headerName))
|
||||
if valid {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
return remoteIP.String()
|
||||
}
|
||||
|
||||
// RemoteIP parses the IP from Request.RemoteAddr, normalizes and returns the IP (without the port).
|
||||
// It also checks if the remoteIP is a trusted proxy or not.
|
||||
// In order to perform this validation, it will see if the IP is contained within at least one of the CIDR blocks
|
||||
// defined in Engine.TrustedProxies
|
||||
func (c *Context) RemoteIP() (net.IP, bool) {
|
||||
ip, _, err := net.SplitHostPort(strings.TrimSpace(c.Request.RemoteAddr))
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
remoteIP := net.ParseIP(ip)
|
||||
if remoteIP == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
trustedCIDRs, _ := c.engine.prepareTrustedCIDRs()
|
||||
c.engine.trustedCIDRs = trustedCIDRs
|
||||
if c.engine.trustedCIDRs != nil {
|
||||
for _, cidr := range c.engine.trustedCIDRs {
|
||||
if cidr.Contains(remoteIP) {
|
||||
return remoteIP, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return remoteIP, false
|
||||
}
|
||||
|
||||
func validateHeader(header string) (clientIP string, valid bool) {
|
||||
if header == "" {
|
||||
return "", false
|
||||
}
|
||||
items := strings.Split(header, ",")
|
||||
for i, ipStr := range items {
|
||||
ipStr = strings.TrimSpace(ipStr)
|
||||
ip := net.ParseIP(ipStr)
|
||||
if ip == nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// We need to return the first IP in the list, but,
|
||||
// we should not early return since we need to validate that
|
||||
// the rest of the header is syntactically valid
|
||||
if i == 0 {
|
||||
clientIP = ipStr
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ContentType returns the Content-Type header of the request.
|
||||
@@ -772,6 +865,11 @@ func (c *Context) GetRawData() ([]byte, error) {
|
||||
return ioutil.ReadAll(c.Request.Body)
|
||||
}
|
||||
|
||||
// SetSameSite with cookie
|
||||
func (c *Context) SetSameSite(samesite http.SameSite) {
|
||||
c.sameSite = samesite
|
||||
}
|
||||
|
||||
// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
|
||||
// The provided cookie must have a valid Name. Invalid cookies may be
|
||||
// silently dropped.
|
||||
@@ -785,6 +883,7 @@ func (c *Context) SetCookie(name, value string, maxAge int, path, domain string,
|
||||
MaxAge: maxAge,
|
||||
Path: path,
|
||||
Domain: domain,
|
||||
SameSite: c.sameSite,
|
||||
Secure: secure,
|
||||
HttpOnly: httpOnly,
|
||||
})
|
||||
@@ -838,11 +937,11 @@ func (c *Context) IndentedJSON(code int, obj interface{}) {
|
||||
// Default prepends "while(1)," to response body if the given struct is array values.
|
||||
// It also sets the Content-Type as "application/json".
|
||||
func (c *Context) SecureJSON(code int, obj interface{}) {
|
||||
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
|
||||
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJSONPrefix, Data: obj})
|
||||
}
|
||||
|
||||
// JSONP serializes the given struct as JSON into the response body.
|
||||
// It add padding to response body to request data from a server residing in a different domain than the client.
|
||||
// It adds padding to response body to request data from a server residing in a different domain than the client.
|
||||
// It also sets the Content-Type as "application/javascript".
|
||||
func (c *Context) JSONP(code int, obj interface{}) {
|
||||
callback := c.DefaultQuery("callback", "")
|
||||
@@ -919,15 +1018,26 @@ func (c *Context) DataFromReader(code int, contentLength int64, contentType stri
|
||||
})
|
||||
}
|
||||
|
||||
// File writes the specified file into the body stream in a efficient way.
|
||||
// File writes the specified file into the body stream in an efficient way.
|
||||
func (c *Context) File(filepath string) {
|
||||
http.ServeFile(c.Writer, c.Request, filepath)
|
||||
}
|
||||
|
||||
// FileFromFS writes the specified file from http.FileSystem into the body stream in an efficient way.
|
||||
func (c *Context) FileFromFS(filepath string, fs http.FileSystem) {
|
||||
defer func(old string) {
|
||||
c.Request.URL.Path = old
|
||||
}(c.Request.URL.Path)
|
||||
|
||||
c.Request.URL.Path = filepath
|
||||
|
||||
http.FileServer(fs).ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
|
||||
// FileAttachment writes the specified file into the body stream in an efficient way
|
||||
// On the client side, the file will typically be downloaded with the given filename
|
||||
func (c *Context) FileAttachment(filepath, filename string) {
|
||||
c.Writer.Header().Set("content-disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
|
||||
http.ServeFile(c.Writer, c.Request, filepath)
|
||||
}
|
||||
|
||||
@@ -969,6 +1079,7 @@ type Negotiate struct {
|
||||
HTMLData interface{}
|
||||
JSONData interface{}
|
||||
XMLData interface{}
|
||||
YAMLData interface{}
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
@@ -987,6 +1098,10 @@ func (c *Context) Negotiate(code int, config Negotiate) {
|
||||
data := chooseData(config.XMLData, config.Data)
|
||||
c.XML(code, data)
|
||||
|
||||
case binding.MIMEYAML:
|
||||
data := chooseData(config.YAMLData, config.Data)
|
||||
c.YAML(code, data)
|
||||
|
||||
default:
|
||||
c.AbortWithError(http.StatusNotAcceptable, errors.New("the accepted formats are not offered by the server")) // nolint: errcheck
|
||||
}
|
||||
@@ -1003,20 +1118,20 @@ func (c *Context) NegotiateFormat(offered ...string) string {
|
||||
return offered[0]
|
||||
}
|
||||
for _, accepted := range c.Accepted {
|
||||
for _, offert := range offered {
|
||||
for _, offer := range offered {
|
||||
// According to RFC 2616 and RFC 2396, non-ASCII characters are not allowed in headers,
|
||||
// therefore we can just iterate over the string without casting it into []rune
|
||||
i := 0
|
||||
for ; i < len(accepted); i++ {
|
||||
if accepted[i] == '*' || offert[i] == '*' {
|
||||
return offert
|
||||
if accepted[i] == '*' || offer[i] == '*' {
|
||||
return offer
|
||||
}
|
||||
if accepted[i] != offert[i] {
|
||||
if accepted[i] != offer[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i == len(accepted) {
|
||||
return offert
|
||||
return offer
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1032,26 +1147,20 @@ func (c *Context) SetAccepted(formats ...string) {
|
||||
/***** GOLANG.ORG/X/NET/CONTEXT *****/
|
||||
/************************************/
|
||||
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
// Deadline always returns that there is no deadline (ok==false),
|
||||
// maybe you want to use Request.Context().Deadline() instead.
|
||||
func (c *Context) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
// Done always returns nil (chan which will wait forever),
|
||||
// if you want to abort your work when the connection was closed
|
||||
// you should use Request.Context().Done() instead.
|
||||
func (c *Context) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Err returns a non-nil error value after Done is closed,
|
||||
// successive calls to Err return the same error.
|
||||
// If Done is not yet closed, Err returns nil.
|
||||
// If Done is closed, Err returns a non-nil error explaining why:
|
||||
// Canceled if the context was canceled
|
||||
// or DeadlineExceeded if the context's deadline passed.
|
||||
// Err always returns nil, maybe you want to use Request.Context().Err() instead.
|
||||
func (c *Context) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
// +build appengine
|
||||
|
||||
// Copyright 2017 Manu Martinez-Almeida. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build appengine
|
||||
// +build appengine
|
||||
|
||||
package gin
|
||||
|
||||
func init() {
|
||||
|
||||
+2
-2
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const ginSupportMinGoVer = 10
|
||||
const ginSupportMinGoVer = 12
|
||||
|
||||
// IsDebugging returns true if the framework is running in debug mode.
|
||||
// Use SetMode(gin.ReleaseMode) to disable debug mode.
|
||||
@@ -67,7 +67,7 @@ func getMinVer(v string) (uint64, error) {
|
||||
|
||||
func debugPrintWARNINGDefault() {
|
||||
if v, e := getMinVer(runtime.Version()); e == nil && v <= ginSupportMinGoVer {
|
||||
debugPrint(`[WARNING] Now Gin requires Go 1.10 or later and Go 1.11 will be required soon.
|
||||
debugPrint(`[WARNING] Now Gin requires Go 1.12+.
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
+15
-10
@@ -55,7 +55,7 @@ func (msg *Error) SetMeta(data interface{}) *Error {
|
||||
|
||||
// JSON creates a properly formatted JSON
|
||||
func (msg *Error) JSON() interface{} {
|
||||
json := H{}
|
||||
jsonData := H{}
|
||||
if msg.Meta != nil {
|
||||
value := reflect.ValueOf(msg.Meta)
|
||||
switch value.Kind() {
|
||||
@@ -63,16 +63,16 @@ func (msg *Error) JSON() interface{} {
|
||||
return msg.Meta
|
||||
case reflect.Map:
|
||||
for _, key := range value.MapKeys() {
|
||||
json[key.String()] = value.MapIndex(key).Interface()
|
||||
jsonData[key.String()] = value.MapIndex(key).Interface()
|
||||
}
|
||||
default:
|
||||
json["meta"] = msg.Meta
|
||||
jsonData["meta"] = msg.Meta
|
||||
}
|
||||
}
|
||||
if _, ok := json["error"]; !ok {
|
||||
json["error"] = msg.Error()
|
||||
if _, ok := jsonData["error"]; !ok {
|
||||
jsonData["error"] = msg.Error()
|
||||
}
|
||||
return json
|
||||
return jsonData
|
||||
}
|
||||
|
||||
// MarshalJSON implements the json.Marshaller interface.
|
||||
@@ -90,6 +90,11 @@ func (msg *Error) IsType(flags ErrorType) bool {
|
||||
return (msg.Type & flags) > 0
|
||||
}
|
||||
|
||||
// Unwrap returns the wrapped error, to allow interoperability with errors.Is(), errors.As() and errors.Unwrap()
|
||||
func (msg *Error) Unwrap() error {
|
||||
return msg.Err
|
||||
}
|
||||
|
||||
// ByType returns a readonly copy filtered the byte.
|
||||
// ie ByType(gin.ErrorTypePublic) returns a slice of errors with type=ErrorTypePublic.
|
||||
func (a errorMsgs) ByType(typ ErrorType) errorMsgs {
|
||||
@@ -135,17 +140,17 @@ func (a errorMsgs) Errors() []string {
|
||||
}
|
||||
|
||||
func (a errorMsgs) JSON() interface{} {
|
||||
switch len(a) {
|
||||
switch length := len(a); length {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
return a.Last().JSON()
|
||||
default:
|
||||
json := make([]interface{}, len(a))
|
||||
jsonData := make([]interface{}, length)
|
||||
for i, err := range a {
|
||||
json[i] = err.JSON()
|
||||
jsonData[i] = err.JSON()
|
||||
}
|
||||
return json
|
||||
return jsonData
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type onlyfilesFS struct {
|
||||
type onlyFilesFS struct {
|
||||
fs http.FileSystem
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ func Dir(root string, listDirectory bool) http.FileSystem {
|
||||
if listDirectory {
|
||||
return fs
|
||||
}
|
||||
return &onlyfilesFS{fs}
|
||||
return &onlyFilesFS{fs}
|
||||
}
|
||||
|
||||
// Open conforms to http.Filesystem.
|
||||
func (fs onlyfilesFS) Open(name string) (http.File, error) {
|
||||
func (fs onlyFilesFS) Open(name string) (http.File, error) {
|
||||
f, err := fs.fs.Open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+121
-34
@@ -11,19 +11,22 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
"github.com/gin-gonic/gin/render"
|
||||
)
|
||||
|
||||
const defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
|
||||
var (
|
||||
default404Body = []byte("404 page not found")
|
||||
default405Body = []byte("405 method not allowed")
|
||||
defaultAppEngine bool
|
||||
default404Body = []byte("404 page not found")
|
||||
default405Body = []byte("405 method not allowed")
|
||||
)
|
||||
|
||||
var defaultAppEngine bool
|
||||
|
||||
// HandlerFunc defines the handler used by gin middleware as return value.
|
||||
type HandlerFunc func(*Context)
|
||||
|
||||
@@ -79,9 +82,26 @@ type Engine struct {
|
||||
// If no other Method is allowed, the request is delegated to the NotFound
|
||||
// handler.
|
||||
HandleMethodNotAllowed bool
|
||||
ForwardedByClientIP bool
|
||||
|
||||
// #726 #755 If enabled, it will thrust some headers starting with
|
||||
// If enabled, client IP will be parsed from the request's headers that
|
||||
// match those stored at `(*gin.Engine).RemoteIPHeaders`. If no IP was
|
||||
// fetched, it falls back to the IP obtained from
|
||||
// `(*gin.Context).Request.RemoteAddr`.
|
||||
ForwardedByClientIP bool
|
||||
|
||||
// List of headers used to obtain the client IP when
|
||||
// `(*gin.Engine).ForwardedByClientIP` is `true` and
|
||||
// `(*gin.Context).Request.RemoteAddr` is matched by at least one of the
|
||||
// network origins of `(*gin.Engine).TrustedProxies`.
|
||||
RemoteIPHeaders []string
|
||||
|
||||
// List of network origins (IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
||||
// IPv6 CIDRs) from which to trust request's headers that contain
|
||||
// alternative client IP when `(*gin.Engine).ForwardedByClientIP` is
|
||||
// `true`.
|
||||
TrustedProxies []string
|
||||
|
||||
// #726 #755 If enabled, it will trust some headers starting with
|
||||
// 'X-AppEngine...' for better integration with that PaaS.
|
||||
AppEngine bool
|
||||
|
||||
@@ -97,8 +117,12 @@ type Engine struct {
|
||||
// method call.
|
||||
MaxMultipartMemory int64
|
||||
|
||||
// RemoveExtraSlash a parameter can be parsed from the URL even with extra slashes.
|
||||
// See the PR #1817 and issue #1644
|
||||
RemoveExtraSlash bool
|
||||
|
||||
delims render.Delims
|
||||
secureJsonPrefix string
|
||||
secureJSONPrefix string
|
||||
HTMLRender render.HTMLRender
|
||||
FuncMap template.FuncMap
|
||||
allNoRoute HandlersChain
|
||||
@@ -107,6 +131,8 @@ type Engine struct {
|
||||
noMethod HandlersChain
|
||||
pool sync.Pool
|
||||
trees methodTrees
|
||||
maxParams uint16
|
||||
trustedCIDRs []*net.IPNet
|
||||
}
|
||||
|
||||
var _ IRouter = &Engine{}
|
||||
@@ -132,13 +158,16 @@ func New() *Engine {
|
||||
RedirectFixedPath: false,
|
||||
HandleMethodNotAllowed: false,
|
||||
ForwardedByClientIP: true,
|
||||
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
|
||||
TrustedProxies: []string{"0.0.0.0/0"},
|
||||
AppEngine: defaultAppEngine,
|
||||
UseRawPath: false,
|
||||
RemoveExtraSlash: false,
|
||||
UnescapePathValues: true,
|
||||
MaxMultipartMemory: defaultMultipartMemory,
|
||||
trees: make(methodTrees, 0, 9),
|
||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||
secureJsonPrefix: "while(1);",
|
||||
secureJSONPrefix: "while(1);",
|
||||
}
|
||||
engine.RouterGroup.engine = engine
|
||||
engine.pool.New = func() interface{} {
|
||||
@@ -156,7 +185,8 @@ func Default() *Engine {
|
||||
}
|
||||
|
||||
func (engine *Engine) allocateContext() *Context {
|
||||
return &Context{engine: engine}
|
||||
v := make(Params, 0, engine.maxParams)
|
||||
return &Context{engine: engine, params: &v}
|
||||
}
|
||||
|
||||
// Delims sets template left and right delims and returns a Engine instance.
|
||||
@@ -165,9 +195,9 @@ func (engine *Engine) Delims(left, right string) *Engine {
|
||||
return engine
|
||||
}
|
||||
|
||||
// SecureJsonPrefix sets the secureJsonPrefix used in Context.SecureJSON.
|
||||
// SecureJsonPrefix sets the secureJSONPrefix used in Context.SecureJSON.
|
||||
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
||||
engine.secureJsonPrefix = prefix
|
||||
engine.secureJSONPrefix = prefix
|
||||
return engine
|
||||
}
|
||||
|
||||
@@ -249,6 +279,7 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||
assert1(len(handlers) > 0, "there must be at least one handler")
|
||||
|
||||
debugPrintRoute(method, path, handlers)
|
||||
|
||||
root := engine.trees.get(method)
|
||||
if root == nil {
|
||||
root = new(node)
|
||||
@@ -256,6 +287,11 @@ func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
|
||||
engine.trees = append(engine.trees, methodTree{method: method, root: root})
|
||||
}
|
||||
root.addRoute(path, handlers)
|
||||
|
||||
// Update maxParams
|
||||
if paramsCount := countParams(path); paramsCount > engine.maxParams {
|
||||
engine.maxParams = paramsCount
|
||||
}
|
||||
}
|
||||
|
||||
// Routes returns a slice of registered routes, including some useful information, such as:
|
||||
@@ -290,12 +326,60 @@ func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||
func (engine *Engine) Run(addr ...string) (err error) {
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
trustedCIDRs, err := engine.prepareTrustedCIDRs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
engine.trustedCIDRs = trustedCIDRs
|
||||
address := resolveAddress(addr)
|
||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||
err = http.ListenAndServe(address, engine)
|
||||
return
|
||||
}
|
||||
|
||||
func (engine *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
|
||||
if engine.TrustedProxies == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cidr := make([]*net.IPNet, 0, len(engine.TrustedProxies))
|
||||
for _, trustedProxy := range engine.TrustedProxies {
|
||||
if !strings.Contains(trustedProxy, "/") {
|
||||
ip := parseIP(trustedProxy)
|
||||
if ip == nil {
|
||||
return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy}
|
||||
}
|
||||
|
||||
switch len(ip) {
|
||||
case net.IPv4len:
|
||||
trustedProxy += "/32"
|
||||
case net.IPv6len:
|
||||
trustedProxy += "/128"
|
||||
}
|
||||
}
|
||||
_, cidrNet, err := net.ParseCIDR(trustedProxy)
|
||||
if err != nil {
|
||||
return cidr, err
|
||||
}
|
||||
cidr = append(cidr, cidrNet)
|
||||
}
|
||||
return cidr, nil
|
||||
}
|
||||
|
||||
// parseIP parse a string representation of an IP and returns a net.IP with the
|
||||
// minimum byte representation or nil if input is invalid.
|
||||
func parseIP(ip string) net.IP {
|
||||
parsedIP := net.ParseIP(ip)
|
||||
|
||||
if ipv4 := parsedIP.To4(); ipv4 != nil {
|
||||
// return ip in a 4-byte representation
|
||||
return ipv4
|
||||
}
|
||||
|
||||
// return ip in a 16-byte representation or nil
|
||||
return parsedIP
|
||||
}
|
||||
|
||||
// RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests.
|
||||
// It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router)
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
@@ -314,16 +398,13 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
||||
debugPrint("Listening and serving HTTP on unix:/%s", file)
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
os.Remove(file)
|
||||
listener, err := net.Listen("unix", file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer listener.Close()
|
||||
err = os.Chmod(file, 0777)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer os.Remove(file)
|
||||
|
||||
err = http.Serve(listener, engine)
|
||||
return
|
||||
}
|
||||
@@ -385,7 +466,10 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
rPath = c.Request.URL.RawPath
|
||||
unescape = engine.UnescapePathValues
|
||||
}
|
||||
rPath = cleanPath(rPath)
|
||||
|
||||
if engine.RemoveExtraSlash {
|
||||
rPath = cleanPath(rPath)
|
||||
}
|
||||
|
||||
// Find root of the tree for the given HTTP method
|
||||
t := engine.trees
|
||||
@@ -395,10 +479,12 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
}
|
||||
root := t[i].root
|
||||
// Find route in tree
|
||||
value := root.getValue(rPath, c.Params, unescape)
|
||||
value := root.getValue(rPath, c.params, unescape)
|
||||
if value.params != nil {
|
||||
c.Params = *value.params
|
||||
}
|
||||
if value.handlers != nil {
|
||||
c.handlers = value.handlers
|
||||
c.Params = value.params
|
||||
c.fullPath = value.fullPath
|
||||
c.Next()
|
||||
c.writermem.WriteHeaderNow()
|
||||
@@ -457,18 +543,11 @@ func redirectTrailingSlash(c *Context) {
|
||||
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
|
||||
p = prefix + "/" + req.URL.Path
|
||||
}
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != "GET" {
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
|
||||
req.URL.Path = p + "/"
|
||||
if length := len(p); length > 1 && p[length-1] == '/' {
|
||||
req.URL.Path = p[:length-1]
|
||||
}
|
||||
debugPrint("redirecting request %d: %s --> %s", code, p, req.URL.String())
|
||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
redirectRequest(c)
|
||||
}
|
||||
|
||||
func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||
@@ -476,15 +555,23 @@ func redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
|
||||
rPath := req.URL.Path
|
||||
|
||||
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != "GET" {
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
req.URL.Path = string(fixedPath)
|
||||
debugPrint("redirecting request %d: %s --> %s", code, rPath, req.URL.String())
|
||||
http.Redirect(c.Writer, req, req.URL.String(), code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
req.URL.Path = bytesconv.BytesToString(fixedPath)
|
||||
redirectRequest(c)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func redirectRequest(c *Context) {
|
||||
req := c.Request
|
||||
rPath := req.URL.Path
|
||||
rURL := req.URL.String()
|
||||
|
||||
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
|
||||
if req.Method != http.MethodGet {
|
||||
code = http.StatusTemporaryRedirect
|
||||
}
|
||||
debugPrint("redirecting request %d: %s --> %s", code, rPath, rURL)
|
||||
http.Redirect(c.Writer, req, rURL, code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
}
|
||||
|
||||
+6
-10
@@ -1,18 +1,14 @@
|
||||
module github.com/gin-gonic/gin
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/sse v0.1.0
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/golang/protobuf v1.3.2
|
||||
github.com/json-iterator/go v1.1.7
|
||||
github.com/leodido/go-urn v1.1.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9
|
||||
github.com/go-playground/validator/v10 v10.4.1
|
||||
github.com/golang/protobuf v1.3.3
|
||||
github.com/json-iterator/go v1.1.9
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/ugorji/go/codec v1.1.7
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
|
||||
+29
-19
@@ -3,19 +3,23 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/leodido/go-urn v1.1.0 h1:Sm1gr51B1kKyfD2BlRcLSiEkffoG96g6TPv6eRoEiB8=
|
||||
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
|
||||
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
|
||||
@@ -30,13 +34,19 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Copyright 2020 Gin Core Team. All rights reserved.
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bytesconv
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// StringToBytes converts string to byte slice without a memory allocation.
|
||||
func StringToBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(
|
||||
&struct {
|
||||
string
|
||||
Cap int
|
||||
}{s, len(s)},
|
||||
))
|
||||
}
|
||||
|
||||
// BytesToString converts byte slice to string without a memory allocation.
|
||||
func BytesToString(b []byte) string {
|
||||
return *(*string)(unsafe.Pointer(&b))
|
||||
}
|
||||
+1
@@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !jsoniter
|
||||
// +build !jsoniter
|
||||
|
||||
package json
|
||||
|
||||
+2
-1
@@ -2,11 +2,12 @@
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build jsoniter
|
||||
// +build jsoniter
|
||||
|
||||
package json
|
||||
|
||||
import "github.com/json-iterator/go"
|
||||
import jsoniter "github.com/json-iterator/go"
|
||||
|
||||
var (
|
||||
json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
|
||||
+8
-8
@@ -99,19 +99,19 @@ func (p *LogFormatterParams) MethodColor() string {
|
||||
method := p.Method
|
||||
|
||||
switch method {
|
||||
case "GET":
|
||||
case http.MethodGet:
|
||||
return blue
|
||||
case "POST":
|
||||
case http.MethodPost:
|
||||
return cyan
|
||||
case "PUT":
|
||||
case http.MethodPut:
|
||||
return yellow
|
||||
case "DELETE":
|
||||
case http.MethodDelete:
|
||||
return red
|
||||
case "PATCH":
|
||||
case http.MethodPatch:
|
||||
return green
|
||||
case "HEAD":
|
||||
case http.MethodHead:
|
||||
return magenta
|
||||
case "OPTIONS":
|
||||
case http.MethodOptions:
|
||||
return white
|
||||
default:
|
||||
return reset
|
||||
@@ -141,7 +141,7 @@ var defaultLogFormatter = func(param LogFormatterParams) string {
|
||||
// Truncate in a golang < 1.8 safe way
|
||||
param.Latency = param.Latency - param.Latency%time.Second
|
||||
}
|
||||
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %s\n%s",
|
||||
return fmt.Sprintf("[GIN] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v\n%s",
|
||||
param.TimeStamp.Format("2006/01/02 - 15:04:05"),
|
||||
statusColor, param.StatusCode, resetColor,
|
||||
param.Latency,
|
||||
|
||||
+9
-6
@@ -22,6 +22,7 @@ const (
|
||||
// TestMode indicates gin mode is test.
|
||||
TestMode = "test"
|
||||
)
|
||||
|
||||
const (
|
||||
debugCode = iota
|
||||
releaseCode
|
||||
@@ -50,19 +51,21 @@ func init() {
|
||||
|
||||
// SetMode sets gin mode according to input string.
|
||||
func SetMode(value string) {
|
||||
if value == "" {
|
||||
value = DebugMode
|
||||
}
|
||||
|
||||
switch value {
|
||||
case DebugMode, "":
|
||||
case DebugMode:
|
||||
ginMode = debugCode
|
||||
case ReleaseMode:
|
||||
ginMode = releaseCode
|
||||
case TestMode:
|
||||
ginMode = testCode
|
||||
default:
|
||||
panic("gin mode unknown: " + value)
|
||||
}
|
||||
if value == "" {
|
||||
value = DebugMode
|
||||
panic("gin mode unknown: " + value + " (available mode: debug release test)")
|
||||
}
|
||||
|
||||
modeName = value
|
||||
}
|
||||
|
||||
@@ -77,7 +80,7 @@ func EnableJsonDecoderUseNumber() {
|
||||
binding.EnableDecoderUseNumber = true
|
||||
}
|
||||
|
||||
// EnableJsonDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
|
||||
// EnableJsonDecoderDisallowUnknownFields sets true for binding.EnableDecoderDisallowUnknownFields to
|
||||
// call the DisallowUnknownFields method on the JSON Decoder instance.
|
||||
func EnableJsonDecoderDisallowUnknownFields() {
|
||||
binding.EnableDecoderDisallowUnknownFields = true
|
||||
|
||||
+42
-15
@@ -19,13 +19,17 @@ package gin
|
||||
//
|
||||
// If the result of this process is an empty string, "/" is returned.
|
||||
func cleanPath(p string) string {
|
||||
const stackBufSize = 128
|
||||
// Turn empty string into "/"
|
||||
if p == "" {
|
||||
return "/"
|
||||
}
|
||||
|
||||
// Reasonably sized buffer on stack to avoid allocations in the common case.
|
||||
// If a larger buffer is required, it gets allocated dynamically.
|
||||
buf := make([]byte, 0, stackBufSize)
|
||||
|
||||
n := len(p)
|
||||
var buf []byte
|
||||
|
||||
// Invariants:
|
||||
// reading from path; r is index of next byte to process.
|
||||
@@ -37,15 +41,21 @@ func cleanPath(p string) string {
|
||||
|
||||
if p[0] != '/' {
|
||||
r = 0
|
||||
buf = make([]byte, n+1)
|
||||
|
||||
if n+1 > stackBufSize {
|
||||
buf = make([]byte, n+1)
|
||||
} else {
|
||||
buf = buf[:n+1]
|
||||
}
|
||||
buf[0] = '/'
|
||||
}
|
||||
|
||||
trailing := n > 1 && p[n-1] == '/'
|
||||
|
||||
// A bit more clunky without a 'lazybuf' like the path package, but the loop
|
||||
// gets completely inlined (bufApp). So in contrast to the path package this
|
||||
// loop has no expensive function calls (except 1x make)
|
||||
// gets completely inlined (bufApp calls).
|
||||
// loop has no expensive function calls (except 1x make) // So in contrast to the path package this loop has no expensive function
|
||||
// calls (except make, if needed).
|
||||
|
||||
for r < n {
|
||||
switch {
|
||||
@@ -69,7 +79,7 @@ func cleanPath(p string) string {
|
||||
// can backtrack
|
||||
w--
|
||||
|
||||
if buf == nil {
|
||||
if len(buf) == 0 {
|
||||
for w > 1 && p[w] != '/' {
|
||||
w--
|
||||
}
|
||||
@@ -81,14 +91,14 @@ func cleanPath(p string) string {
|
||||
}
|
||||
|
||||
default:
|
||||
// real path element.
|
||||
// add slash if needed
|
||||
// Real path element.
|
||||
// Add slash if needed
|
||||
if w > 1 {
|
||||
bufApp(&buf, p, w, '/')
|
||||
w++
|
||||
}
|
||||
|
||||
// copy element
|
||||
// Copy element
|
||||
for r < n && p[r] != '/' {
|
||||
bufApp(&buf, p, w, p[r])
|
||||
w++
|
||||
@@ -97,27 +107,44 @@ func cleanPath(p string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// re-append trailing slash
|
||||
// Re-append trailing slash
|
||||
if trailing && w > 1 {
|
||||
bufApp(&buf, p, w, '/')
|
||||
w++
|
||||
}
|
||||
|
||||
if buf == nil {
|
||||
// If the original string was not modified (or only shortened at the end),
|
||||
// return the respective substring of the original string.
|
||||
// Otherwise return a new string from the buffer.
|
||||
if len(buf) == 0 {
|
||||
return p[:w]
|
||||
}
|
||||
return string(buf[:w])
|
||||
}
|
||||
|
||||
// internal helper to lazily create a buffer if necessary.
|
||||
// Internal helper to lazily create a buffer if necessary.
|
||||
// Calls to this function get inlined.
|
||||
func bufApp(buf *[]byte, s string, w int, c byte) {
|
||||
if *buf == nil {
|
||||
b := *buf
|
||||
if len(b) == 0 {
|
||||
// No modification of the original string so far.
|
||||
// If the next character is the same as in the original string, we do
|
||||
// not yet have to allocate a buffer.
|
||||
if s[w] == c {
|
||||
return
|
||||
}
|
||||
|
||||
*buf = make([]byte, len(s))
|
||||
copy(*buf, s[:w])
|
||||
// Otherwise use either the stack buffer, if it is large enough, or
|
||||
// allocate a new buffer on the heap, and copy all previous characters.
|
||||
length := len(s)
|
||||
if length > cap(b) {
|
||||
*buf = make([]byte, length)
|
||||
} else {
|
||||
*buf = (*buf)[:length]
|
||||
}
|
||||
b = *buf
|
||||
|
||||
copy(b, s[:w])
|
||||
}
|
||||
(*buf)[w] = c
|
||||
b[w] = c
|
||||
}
|
||||
|
||||
+27
-7
@@ -26,13 +26,29 @@ var (
|
||||
slash = []byte("/")
|
||||
)
|
||||
|
||||
// RecoveryFunc defines the function passable to CustomRecovery.
|
||||
type RecoveryFunc func(c *Context, err interface{})
|
||||
|
||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||
func Recovery() HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter)
|
||||
}
|
||||
|
||||
//CustomRecovery returns a middleware that recovers from any panics and calls the provided handle func to handle it.
|
||||
func CustomRecovery(handle RecoveryFunc) HandlerFunc {
|
||||
return RecoveryWithWriter(DefaultErrorWriter, handle)
|
||||
}
|
||||
|
||||
// RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
|
||||
func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||
func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
|
||||
if len(recovery) > 0 {
|
||||
return CustomRecoveryWithWriter(out, recovery[0])
|
||||
}
|
||||
return CustomRecoveryWithWriter(out, defaultHandleRecovery)
|
||||
}
|
||||
|
||||
// CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
|
||||
func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||
var logger *log.Logger
|
||||
if out != nil {
|
||||
logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
|
||||
@@ -60,23 +76,23 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||
headers[idx] = current[0] + ": *"
|
||||
}
|
||||
}
|
||||
headersToStr := strings.Join(headers, "\r\n")
|
||||
if brokenPipe {
|
||||
logger.Printf("%s\n%s%s", err, string(httpRequest), reset)
|
||||
logger.Printf("%s\n%s%s", err, headersToStr, reset)
|
||||
} else if IsDebugging() {
|
||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
|
||||
timeFormat(time.Now()), strings.Join(headers, "\r\n"), err, stack, reset)
|
||||
timeFormat(time.Now()), headersToStr, err, stack, reset)
|
||||
} else {
|
||||
logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
|
||||
timeFormat(time.Now()), err, stack, reset)
|
||||
}
|
||||
}
|
||||
|
||||
// If the connection is dead, we can't write a status to it.
|
||||
if brokenPipe {
|
||||
// If the connection is dead, we can't write a status to it.
|
||||
c.Error(err.(error)) // nolint: errcheck
|
||||
c.Abort()
|
||||
} else {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
handle(c, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@@ -84,6 +100,10 @@ func RecoveryWithWriter(out io.Writer) HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func defaultHandleRecovery(c *Context, err interface{}) {
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
// stack returns a nicely formatted stack frame, skipping skip frames.
|
||||
func stack(skip int) []byte {
|
||||
buf := new(bytes.Buffer) // the returned data
|
||||
@@ -146,6 +166,6 @@ func function(pc uintptr) []byte {
|
||||
}
|
||||
|
||||
func timeFormat(t time.Time) string {
|
||||
var timeString = t.Format("2006/01/02 - 15:04:05")
|
||||
timeString := t.Format("2006/01/02 - 15:04:05")
|
||||
return timeString
|
||||
}
|
||||
|
||||
+13
-11
@@ -10,6 +10,7 @@ import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
"github.com/gin-gonic/gin/internal/json"
|
||||
)
|
||||
|
||||
@@ -40,9 +41,6 @@ type AsciiJSON struct {
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// SecureJSONPrefix is a string which represents SecureJSON prefix.
|
||||
type SecureJSONPrefix string
|
||||
|
||||
// PureJSON contains the given interface object.
|
||||
type PureJSON struct {
|
||||
Data interface{}
|
||||
@@ -68,8 +66,11 @@ func (r JSON) WriteContentType(w http.ResponseWriter) {
|
||||
// WriteJSON marshals the given interface object and writes it with custom ContentType.
|
||||
func WriteJSON(w http.ResponseWriter, obj interface{}) error {
|
||||
writeContentType(w, jsonContentType)
|
||||
encoder := json.NewEncoder(w)
|
||||
err := encoder.Encode(&obj)
|
||||
jsonBytes, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(jsonBytes)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -97,8 +98,9 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||
return err
|
||||
}
|
||||
// if the jsonBytes is array values
|
||||
if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) {
|
||||
_, err = w.Write([]byte(r.Prefix))
|
||||
if bytes.HasPrefix(jsonBytes, bytesconv.StringToBytes("[")) && bytes.HasSuffix(jsonBytes,
|
||||
bytesconv.StringToBytes("]")) {
|
||||
_, err = w.Write(bytesconv.StringToBytes(r.Prefix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -126,11 +128,11 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||
}
|
||||
|
||||
callback := template.JSEscapeString(r.Callback)
|
||||
_, err = w.Write([]byte(callback))
|
||||
_, err = w.Write(bytesconv.StringToBytes(callback))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write([]byte("("))
|
||||
_, err = w.Write(bytesconv.StringToBytes("("))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -138,7 +140,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write([]byte(");"))
|
||||
_, err = w.Write(bytesconv.StringToBytes(");"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -160,7 +162,7 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
for _, r := range string(ret) {
|
||||
for _, r := range bytesconv.BytesToString(ret) {
|
||||
cvt := string(r)
|
||||
if r >= 128 {
|
||||
cvt = fmt.Sprintf("\\u%04x", int64(r))
|
||||
|
||||
+7
@@ -2,6 +2,9 @@
|
||||
// Use of this source code is governed by a MIT style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !nomsgpack
|
||||
// +build !nomsgpack
|
||||
|
||||
package render
|
||||
|
||||
import (
|
||||
@@ -10,6 +13,10 @@ import (
|
||||
"github.com/ugorji/go/codec"
|
||||
)
|
||||
|
||||
var (
|
||||
_ Render = MsgPack{}
|
||||
)
|
||||
|
||||
// MsgPack contains the given interface object.
|
||||
type MsgPack struct {
|
||||
Data interface{}
|
||||
|
||||
+3
@@ -22,6 +22,9 @@ type Reader struct {
|
||||
func (r Reader) Render(w http.ResponseWriter) (err error) {
|
||||
r.WriteContentType(w)
|
||||
if r.ContentLength >= 0 {
|
||||
if r.Headers == nil {
|
||||
r.Headers = map[string]string{}
|
||||
}
|
||||
r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10)
|
||||
}
|
||||
r.writeHeaders(w, r.Headers)
|
||||
|
||||
-1
@@ -27,7 +27,6 @@ var (
|
||||
_ HTMLRender = HTMLDebug{}
|
||||
_ HTMLRender = HTMLProduction{}
|
||||
_ Render = YAML{}
|
||||
_ Render = MsgPack{}
|
||||
_ Render = Reader{}
|
||||
_ Render = AsciiJSON{}
|
||||
_ Render = ProtoBuf{}
|
||||
|
||||
+3
-2
@@ -6,8 +6,9 @@ package render
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
)
|
||||
|
||||
// String contains the given interface object slice and its format.
|
||||
@@ -35,6 +36,6 @@ func WriteString(w http.ResponseWriter, format string, data []interface{}) (err
|
||||
_, err = fmt.Fprintf(w, format, data...)
|
||||
return
|
||||
}
|
||||
_, err = io.WriteString(w, format)
|
||||
_, err = w.Write(bytesconv.StringToBytes(format))
|
||||
return
|
||||
}
|
||||
|
||||
+17
-17
@@ -95,51 +95,51 @@ func (group *RouterGroup) Handle(httpMethod, relativePath string, handlers ...Ha
|
||||
|
||||
// POST is a shortcut for router.Handle("POST", path, handle).
|
||||
func (group *RouterGroup) POST(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("POST", relativePath, handlers)
|
||||
return group.handle(http.MethodPost, relativePath, handlers)
|
||||
}
|
||||
|
||||
// GET is a shortcut for router.Handle("GET", path, handle).
|
||||
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("GET", relativePath, handlers)
|
||||
return group.handle(http.MethodGet, relativePath, handlers)
|
||||
}
|
||||
|
||||
// DELETE is a shortcut for router.Handle("DELETE", path, handle).
|
||||
func (group *RouterGroup) DELETE(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("DELETE", relativePath, handlers)
|
||||
return group.handle(http.MethodDelete, relativePath, handlers)
|
||||
}
|
||||
|
||||
// PATCH is a shortcut for router.Handle("PATCH", path, handle).
|
||||
func (group *RouterGroup) PATCH(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("PATCH", relativePath, handlers)
|
||||
return group.handle(http.MethodPatch, relativePath, handlers)
|
||||
}
|
||||
|
||||
// PUT is a shortcut for router.Handle("PUT", path, handle).
|
||||
func (group *RouterGroup) PUT(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("PUT", relativePath, handlers)
|
||||
return group.handle(http.MethodPut, relativePath, handlers)
|
||||
}
|
||||
|
||||
// OPTIONS is a shortcut for router.Handle("OPTIONS", path, handle).
|
||||
func (group *RouterGroup) OPTIONS(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("OPTIONS", relativePath, handlers)
|
||||
return group.handle(http.MethodOptions, relativePath, handlers)
|
||||
}
|
||||
|
||||
// HEAD is a shortcut for router.Handle("HEAD", path, handle).
|
||||
func (group *RouterGroup) HEAD(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
return group.handle("HEAD", relativePath, handlers)
|
||||
return group.handle(http.MethodHead, relativePath, handlers)
|
||||
}
|
||||
|
||||
// Any registers a route that matches all the HTTP methods.
|
||||
// GET, POST, PUT, PATCH, HEAD, OPTIONS, DELETE, CONNECT, TRACE.
|
||||
func (group *RouterGroup) Any(relativePath string, handlers ...HandlerFunc) IRoutes {
|
||||
group.handle("GET", relativePath, handlers)
|
||||
group.handle("POST", relativePath, handlers)
|
||||
group.handle("PUT", relativePath, handlers)
|
||||
group.handle("PATCH", relativePath, handlers)
|
||||
group.handle("HEAD", relativePath, handlers)
|
||||
group.handle("OPTIONS", relativePath, handlers)
|
||||
group.handle("DELETE", relativePath, handlers)
|
||||
group.handle("CONNECT", relativePath, handlers)
|
||||
group.handle("TRACE", relativePath, handlers)
|
||||
group.handle(http.MethodGet, relativePath, handlers)
|
||||
group.handle(http.MethodPost, relativePath, handlers)
|
||||
group.handle(http.MethodPut, relativePath, handlers)
|
||||
group.handle(http.MethodPatch, relativePath, handlers)
|
||||
group.handle(http.MethodHead, relativePath, handlers)
|
||||
group.handle(http.MethodOptions, relativePath, handlers)
|
||||
group.handle(http.MethodDelete, relativePath, handlers)
|
||||
group.handle(http.MethodConnect, relativePath, handlers)
|
||||
group.handle(http.MethodTrace, relativePath, handlers)
|
||||
return group.returnObj()
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS
|
||||
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
|
||||
|
||||
return func(c *Context) {
|
||||
if _, nolisting := fs.(*onlyfilesFS); nolisting {
|
||||
if _, noListing := fs.(*onlyFilesFS); noListing {
|
||||
c.Writer.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
|
||||
|
||||
+488
-362
File diff suppressed because it is too large
Load Diff
+10
-8
@@ -90,20 +90,23 @@ func filterFlags(content string) string {
|
||||
}
|
||||
|
||||
func chooseData(custom, wildcard interface{}) interface{} {
|
||||
if custom == nil {
|
||||
if wildcard == nil {
|
||||
panic("negotiation config is invalid")
|
||||
}
|
||||
if custom != nil {
|
||||
return custom
|
||||
}
|
||||
if wildcard != nil {
|
||||
return wildcard
|
||||
}
|
||||
return custom
|
||||
panic("negotiation config is invalid")
|
||||
}
|
||||
|
||||
func parseAccept(acceptHeader string) []string {
|
||||
parts := strings.Split(acceptHeader, ",")
|
||||
out := make([]string, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
if part = strings.TrimSpace(strings.Split(part, ";")[0]); part != "" {
|
||||
if i := strings.IndexByte(part, ';'); i > 0 {
|
||||
part = part[:i]
|
||||
}
|
||||
if part = strings.TrimSpace(part); part != "" {
|
||||
out = append(out, part)
|
||||
}
|
||||
}
|
||||
@@ -127,8 +130,7 @@ func joinPaths(absolutePath, relativePath string) string {
|
||||
}
|
||||
|
||||
finalPath := path.Join(absolutePath, relativePath)
|
||||
appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath) != '/'
|
||||
if appendSlash {
|
||||
if lastChar(relativePath) == '/' && lastChar(finalPath) != '/' {
|
||||
return finalPath + "/"
|
||||
}
|
||||
return finalPath
|
||||
|
||||
+1
-1
@@ -5,4 +5,4 @@
|
||||
package gin
|
||||
|
||||
// Version is the current gin framework's version.
|
||||
const Version = "v1.5.0"
|
||||
const Version = "v1.7.0"
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.13.1
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients: dean.karn@gmail.com
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
before_install:
|
||||
- go install github.com/mattn/goveralls
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
script:
|
||||
- go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./...
|
||||
|
||||
after_success: |
|
||||
goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
## locales
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/locales/master/logo.png">
|
||||
[](https://semaphoreci.com/joeybloggs/locales)
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/locales/master/logo.png">
|
||||
[](https://travis-ci.org/go-playground/locales)
|
||||
[](https://goreportcard.com/report/github.com/go-playground/locales)
|
||||
[](https://godoc.org/github.com/go-playground/locales)
|
||||

|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module github.com/go-playground/locales
|
||||
|
||||
go 1.13
|
||||
|
||||
require golang.org/x/text v0.3.2
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -21,4 +21,5 @@ _testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
*.prof
|
||||
*.coverprofile
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.13.4
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients: dean.karn@gmail.com
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
before_install:
|
||||
- go install github.com/mattn/goveralls
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
script:
|
||||
- go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./...
|
||||
|
||||
after_success: |
|
||||
[ $TRAVIS_GO_VERSION = 1.13.4 ] &&
|
||||
goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
|
||||
+7
-8
@@ -1,7 +1,6 @@
|
||||
## universal-translator
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/universal-translator/master/logo.png">
|
||||

|
||||
[](https://semaphoreci.com/joeybloggs/universal-translator)
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/universal-translator/master/logo.png">
|
||||
[](https://travis-ci.org/go-playground/universal-translator)
|
||||
[](https://coveralls.io/github/go-playground/universal-translator)
|
||||
[](https://goreportcard.com/report/github.com/go-playground/universal-translator)
|
||||
[](https://godoc.org/github.com/go-playground/universal-translator)
|
||||
@@ -46,9 +45,9 @@ Please see https://godoc.org/github.com/go-playground/universal-translator for u
|
||||
|
||||
##### Examples:
|
||||
|
||||
- [Basic](https://github.com/go-playground/universal-translator/tree/master/examples/basic)
|
||||
- [Full - no files](https://github.com/go-playground/universal-translator/tree/master/examples/full-no-files)
|
||||
- [Full - with files](https://github.com/go-playground/universal-translator/tree/master/examples/full-with-files)
|
||||
- [Basic](https://github.com/go-playground/universal-translator/tree/master/_examples/basic)
|
||||
- [Full - no files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-no-files)
|
||||
- [Full - with files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-with-files)
|
||||
|
||||
File formatting
|
||||
--------------
|
||||
@@ -57,10 +56,10 @@ they are only separated for easy viewing.
|
||||
|
||||
##### Examples:
|
||||
|
||||
- [Formats](https://github.com/go-playground/universal-translator/tree/master/examples/file-formats)
|
||||
- [Formats](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats)
|
||||
|
||||
##### Basic Makeup
|
||||
NOTE: not all fields are needed for all translation types, see [examples](https://github.com/go-playground/universal-translator/tree/master/examples/file-formats)
|
||||
NOTE: not all fields are needed for all translation types, see [examples](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats)
|
||||
```json
|
||||
{
|
||||
"locale": "en",
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module github.com/go-playground/universal-translator
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/go-playground/locales v0.13.0
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
+2
-1
@@ -6,6 +6,7 @@
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
bin
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
@@ -26,4 +27,4 @@ _testmain.go
|
||||
*.out
|
||||
*.txt
|
||||
cover.html
|
||||
README.html
|
||||
README.html
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.15.2
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients: dean.karn@gmail.com
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
before_install:
|
||||
- go install github.com/mattn/goveralls
|
||||
- mkdir -p $GOPATH/src/gopkg.in
|
||||
- ln -s $GOPATH/src/github.com/$TRAVIS_REPO_SLUG $GOPATH/src/gopkg.in/validator.v9
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
script:
|
||||
- go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./...
|
||||
|
||||
after_success: |
|
||||
[ $TRAVIS_GO_VERSION = 1.15.2 ] &&
|
||||
goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
GOCMD=GO111MODULE=on go
|
||||
|
||||
linters-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.21.0; \
|
||||
}
|
||||
|
||||
lint: linters-install
|
||||
$(PWD)/bin/golangci-lint run
|
||||
|
||||
test:
|
||||
$(GOCMD) test -cover -race ./...
|
||||
|
||||
bench:
|
||||
$(GOCMD) test -bench=. -benchmem ./...
|
||||
|
||||
.PHONY: test lint linters-install
|
||||
Generated
Vendored
+160
-14
@@ -1,37 +1,37 @@
|
||||
Package validator
|
||||
================
|
||||
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/logo.png">[](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
[](https://semaphoreci.com/joeybloggs/validator)
|
||||
[](https://coveralls.io/github/go-playground/validator?branch=v9)
|
||||

|
||||
[](https://travis-ci.org/go-playground/validator)
|
||||
[](https://coveralls.io/github/go-playground/validator?branch=master)
|
||||
[](https://goreportcard.com/report/github.com/go-playground/validator)
|
||||
[](https://godoc.org/gopkg.in/go-playground/validator.v9)
|
||||
[](https://pkg.go.dev/github.com/go-playground/validator/v10)
|
||||

|
||||
|
||||
Package validator implements value validations for structs and individual fields based on tags.
|
||||
|
||||
It has the following **unique** features:
|
||||
|
||||
- Cross Field and Cross Struct validations by using validation tags or custom validators.
|
||||
- Cross Field and Cross Struct validations by using validation tags or custom validators.
|
||||
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
|
||||
- Ability to dive into both map keys and values for validation
|
||||
- Ability to dive into both map keys and values for validation
|
||||
- Handles type interface by determining it's underlying type prior to validation.
|
||||
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
|
||||
- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
|
||||
- Extraction of custom defined Field Name e.g. can specify to extract the JSON name while validating and have it available in the resulting FieldError
|
||||
- Customizable i18n aware error messages.
|
||||
- Default validator for the [gin](https://github.com/gin-gonic/gin) web framework; upgrading from v8 to v9 in gin see [here](https://github.com/go-playground/validator/tree/v9/_examples/gin-upgrading-overriding)
|
||||
- Default validator for the [gin](https://github.com/gin-gonic/gin) web framework; upgrading from v8 to v9 in gin see [here](https://github.com/go-playground/validator/tree/master/_examples/gin-upgrading-overriding)
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use go get.
|
||||
|
||||
go get gopkg.in/go-playground/validator.v9
|
||||
go get github.com/go-playground/validator/v10
|
||||
|
||||
Then import the validator package into your own code.
|
||||
|
||||
import "gopkg.in/go-playground/validator.v9"
|
||||
import "github.com/go-playground/validator/v10"
|
||||
|
||||
Error Return Value
|
||||
-------
|
||||
@@ -53,17 +53,163 @@ validationErrors := err.(validator.ValidationErrors)
|
||||
Usage and documentation
|
||||
------
|
||||
|
||||
Please see http://godoc.org/gopkg.in/go-playground/validator.v9 for detailed usage docs.
|
||||
Please see https://godoc.org/github.com/go-playground/validator for detailed usage docs.
|
||||
|
||||
##### Examples:
|
||||
|
||||
- [Simple](https://github.com/go-playground/validator/blob/v9/_examples/simple/main.go)
|
||||
- [Custom Field Types](https://github.com/go-playground/validator/blob/v9/_examples/custom/main.go)
|
||||
- [Struct Level](https://github.com/go-playground/validator/blob/v9/_examples/struct-level/main.go)
|
||||
- [Translations & Custom Errors](https://github.com/go-playground/validator/blob/v9/_examples/translations/main.go)
|
||||
- [Simple](https://github.com/go-playground/validator/blob/master/_examples/simple/main.go)
|
||||
- [Custom Field Types](https://github.com/go-playground/validator/blob/master/_examples/custom/main.go)
|
||||
- [Struct Level](https://github.com/go-playground/validator/blob/master/_examples/struct-level/main.go)
|
||||
- [Translations & Custom Errors](https://github.com/go-playground/validator/blob/master/_examples/translations/main.go)
|
||||
- [Gin upgrade and/or override validator](https://github.com/go-playground/validator/tree/v9/_examples/gin-upgrading-overriding)
|
||||
- [wash - an example application putting it all together](https://github.com/bluesuncorp/wash)
|
||||
|
||||
Baked-in Validations
|
||||
------
|
||||
|
||||
### Fields:
|
||||
|
||||
| Tag | Description |
|
||||
| - | - |
|
||||
| eqcsfield | Field Equals Another Field (relative)|
|
||||
| eqfield | Field Equals Another Field |
|
||||
| fieldcontains | NOT DOCUMENTED IN doc.go |
|
||||
| fieldexcludes | NOT DOCUMENTED IN doc.go |
|
||||
| gtcsfield | Field Greater Than Another Relative Field |
|
||||
| gtecsfield | Field Greater Than or Equal To Another Relative Field |
|
||||
| gtefield | Field Greater Than or Equal To Another Field |
|
||||
| gtfield | Field Greater Than Another Field |
|
||||
| ltcsfield | Less Than Another Relative Field |
|
||||
| ltecsfield | Less Than or Equal To Another Relative Field |
|
||||
| ltefield | Less Than or Equal To Another Field |
|
||||
| ltfield | Less Than Another Field |
|
||||
| necsfield | Field Does Not Equal Another Field (relative) |
|
||||
| nefield | Field Does Not Equal Another Field |
|
||||
|
||||
### Network:
|
||||
|
||||
| Tag | Description |
|
||||
| - | - |
|
||||
| cidr | Classless Inter-Domain Routing CIDR |
|
||||
| cidrv4 | Classless Inter-Domain Routing CIDRv4 |
|
||||
| cidrv6 | Classless Inter-Domain Routing CIDRv6 |
|
||||
| datauri | Data URL |
|
||||
| fqdn | Full Qualified Domain Name (FQDN) |
|
||||
| hostname | Hostname RFC 952 |
|
||||
| hostname_port | HostPort |
|
||||
| hostname_rfc1123 | Hostname RFC 1123 |
|
||||
| ip | Internet Protocol Address IP |
|
||||
| ip4_addr | Internet Protocol Address IPv4 |
|
||||
| ip6_addr |Internet Protocol Address IPv6 |
|
||||
| ip_addr | Internet Protocol Address IP |
|
||||
| ipv4 | Internet Protocol Address IPv4 |
|
||||
| ipv6 | Internet Protocol Address IPv6 |
|
||||
| mac | Media Access Control Address MAC |
|
||||
| tcp4_addr | Transmission Control Protocol Address TCPv4 |
|
||||
| tcp6_addr | Transmission Control Protocol Address TCPv6 |
|
||||
| tcp_addr | Transmission Control Protocol Address TCP |
|
||||
| udp4_addr | User Datagram Protocol Address UDPv4 |
|
||||
| udp6_addr | User Datagram Protocol Address UDPv6 |
|
||||
| udp_addr | User Datagram Protocol Address UDP |
|
||||
| unix_addr | Unix domain socket end point Address |
|
||||
| uri | URI String |
|
||||
| url | URL String |
|
||||
| url_encoded | URL Encoded |
|
||||
| urn_rfc2141 | Urn RFC 2141 String |
|
||||
|
||||
### Strings:
|
||||
|
||||
| Tag | Description |
|
||||
| - | - |
|
||||
| alpha | Alpha Only |
|
||||
| alphanum | Alphanumeric |
|
||||
| alphanumunicode | Alphanumeric Unicode |
|
||||
| alphaunicode | Alpha Unicode |
|
||||
| ascii | ASCII |
|
||||
| contains | Contains |
|
||||
| containsany | Contains Any |
|
||||
| containsrune | Contains Rune |
|
||||
| endswith | Ends With |
|
||||
| lowercase | Lowercase |
|
||||
| multibyte | Multi-Byte Characters |
|
||||
| number | NOT DOCUMENTED IN doc.go |
|
||||
| numeric | Numeric |
|
||||
| printascii | Printable ASCII |
|
||||
| startswith | Starts With |
|
||||
| uppercase | Uppercase |
|
||||
|
||||
### Format:
|
||||
| Tag | Description |
|
||||
| - | - |
|
||||
| base64 | Base64 String |
|
||||
| base64url | Base64URL String |
|
||||
| btc_addr | Bitcoin Address |
|
||||
| btc_addr_bech32 | Bitcoin Bech32 Address (segwit) |
|
||||
| datetime | Datetime |
|
||||
| e164 | e164 formatted phone number |
|
||||
| email | E-mail String
|
||||
| eth_addr | Ethereum Address |
|
||||
| hexadecimal | Hexadecimal String |
|
||||
| hexcolor | Hexcolor String |
|
||||
| hsl | HSL String |
|
||||
| hsla | HSLA String |
|
||||
| html | HTML Tags |
|
||||
| html_encoded | HTML Encoded |
|
||||
| isbn | International Standard Book Number |
|
||||
| isbn10 | International Standard Book Number 10 |
|
||||
| isbn13 | International Standard Book Number 13 |
|
||||
| json | JSON |
|
||||
| latitude | Latitude |
|
||||
| longitude | Longitude |
|
||||
| rgb | RGB String |
|
||||
| rgba | RGBA String |
|
||||
| ssn | Social Security Number SSN |
|
||||
| uuid | Universally Unique Identifier UUID |
|
||||
| uuid3 | Universally Unique Identifier UUID v3 |
|
||||
| uuid3_rfc4122 | Universally Unique Identifier UUID v3 RFC4122 |
|
||||
| uuid4 | Universally Unique Identifier UUID v4 |
|
||||
| uuid4_rfc4122 | Universally Unique Identifier UUID v4 RFC4122 |
|
||||
| uuid5 | Universally Unique Identifier UUID v5 |
|
||||
| uuid5_rfc4122 | Universally Unique Identifier UUID v5 RFC4122 |
|
||||
| uuid_rfc4122 | Universally Unique Identifier UUID RFC4122 |
|
||||
|
||||
### Comparisons:
|
||||
| Tag | Description |
|
||||
| - | - |
|
||||
| eq | Equals |
|
||||
| gt | Greater than|
|
||||
| gte |Greater than or equal |
|
||||
| lt | Less Than |
|
||||
| lte | Less Than or Equal |
|
||||
| ne | Not Equal |
|
||||
|
||||
### Other:
|
||||
| Tag | Description |
|
||||
| - | - |
|
||||
| dir | Directory |
|
||||
| endswith | Ends With |
|
||||
| excludes | Excludes |
|
||||
| excludesall | Excludes All |
|
||||
| excludesrune | Excludes Rune |
|
||||
| file | File path |
|
||||
| isdefault | Is Default |
|
||||
| len | Length |
|
||||
| max | Maximum |
|
||||
| min | Minimum |
|
||||
| oneof | One Of |
|
||||
| required | Required |
|
||||
| required_if | Required If |
|
||||
| required_unless | Required Unless |
|
||||
| required_with | Required With |
|
||||
| required_with_all | Required With All |
|
||||
| required_without | Required Without |
|
||||
| required_without_all | Required Without All |
|
||||
| excluded_with | Excluded With |
|
||||
| excluded_with_all | Excluded With All |
|
||||
| excluded_without | Excluded Without |
|
||||
| excluded_without_all | Excluded Without All |
|
||||
| unique | Unique |
|
||||
|
||||
Benchmarks
|
||||
------
|
||||
###### Run on MacBook Pro (15-inch, 2017) go version go1.10.2 darwin/amd64
|
||||
Generated
Vendored
+454
-168
@@ -4,6 +4,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -15,6 +17,8 @@ import (
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/crypto/sha3"
|
||||
|
||||
urn "github.com/leodido/go-urn"
|
||||
)
|
||||
|
||||
@@ -55,116 +59,135 @@ var (
|
||||
// defines a common or complex set of validation(s) to simplify
|
||||
// adding validation to structs.
|
||||
bakedInAliases = map[string]string{
|
||||
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
|
||||
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
|
||||
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
|
||||
}
|
||||
|
||||
// BakedInValidators is the default map of ValidationFunc
|
||||
// you can add, remove or even replace items to suite your needs,
|
||||
// or even disregard and use your own map if so desired.
|
||||
bakedInValidators = map[string]Func{
|
||||
"required": hasValue,
|
||||
"required_with": requiredWith,
|
||||
"required_with_all": requiredWithAll,
|
||||
"required_without": requiredWithout,
|
||||
"required_without_all": requiredWithoutAll,
|
||||
"isdefault": isDefault,
|
||||
"len": hasLengthOf,
|
||||
"min": hasMinOf,
|
||||
"max": hasMaxOf,
|
||||
"eq": isEq,
|
||||
"ne": isNe,
|
||||
"lt": isLt,
|
||||
"lte": isLte,
|
||||
"gt": isGt,
|
||||
"gte": isGte,
|
||||
"eqfield": isEqField,
|
||||
"eqcsfield": isEqCrossStructField,
|
||||
"necsfield": isNeCrossStructField,
|
||||
"gtcsfield": isGtCrossStructField,
|
||||
"gtecsfield": isGteCrossStructField,
|
||||
"ltcsfield": isLtCrossStructField,
|
||||
"ltecsfield": isLteCrossStructField,
|
||||
"nefield": isNeField,
|
||||
"gtefield": isGteField,
|
||||
"gtfield": isGtField,
|
||||
"ltefield": isLteField,
|
||||
"ltfield": isLtField,
|
||||
"fieldcontains": fieldContains,
|
||||
"fieldexcludes": fieldExcludes,
|
||||
"alpha": isAlpha,
|
||||
"alphanum": isAlphanum,
|
||||
"alphaunicode": isAlphaUnicode,
|
||||
"alphanumunicode": isAlphanumUnicode,
|
||||
"numeric": isNumeric,
|
||||
"number": isNumber,
|
||||
"hexadecimal": isHexadecimal,
|
||||
"hexcolor": isHEXColor,
|
||||
"rgb": isRGB,
|
||||
"rgba": isRGBA,
|
||||
"hsl": isHSL,
|
||||
"hsla": isHSLA,
|
||||
"email": isEmail,
|
||||
"url": isURL,
|
||||
"uri": isURI,
|
||||
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
||||
"file": isFile,
|
||||
"base64": isBase64,
|
||||
"base64url": isBase64URL,
|
||||
"contains": contains,
|
||||
"containsany": containsAny,
|
||||
"containsrune": containsRune,
|
||||
"excludes": excludes,
|
||||
"excludesall": excludesAll,
|
||||
"excludesrune": excludesRune,
|
||||
"startswith": startsWith,
|
||||
"endswith": endsWith,
|
||||
"isbn": isISBN,
|
||||
"isbn10": isISBN10,
|
||||
"isbn13": isISBN13,
|
||||
"eth_addr": isEthereumAddress,
|
||||
"btc_addr": isBitcoinAddress,
|
||||
"btc_addr_bech32": isBitcoinBech32Address,
|
||||
"uuid": isUUID,
|
||||
"uuid3": isUUID3,
|
||||
"uuid4": isUUID4,
|
||||
"uuid5": isUUID5,
|
||||
"uuid_rfc4122": isUUIDRFC4122,
|
||||
"uuid3_rfc4122": isUUID3RFC4122,
|
||||
"uuid4_rfc4122": isUUID4RFC4122,
|
||||
"uuid5_rfc4122": isUUID5RFC4122,
|
||||
"ascii": isASCII,
|
||||
"printascii": isPrintableASCII,
|
||||
"multibyte": hasMultiByteCharacter,
|
||||
"datauri": isDataURI,
|
||||
"latitude": isLatitude,
|
||||
"longitude": isLongitude,
|
||||
"ssn": isSSN,
|
||||
"ipv4": isIPv4,
|
||||
"ipv6": isIPv6,
|
||||
"ip": isIP,
|
||||
"cidrv4": isCIDRv4,
|
||||
"cidrv6": isCIDRv6,
|
||||
"cidr": isCIDR,
|
||||
"tcp4_addr": isTCP4AddrResolvable,
|
||||
"tcp6_addr": isTCP6AddrResolvable,
|
||||
"tcp_addr": isTCPAddrResolvable,
|
||||
"udp4_addr": isUDP4AddrResolvable,
|
||||
"udp6_addr": isUDP6AddrResolvable,
|
||||
"udp_addr": isUDPAddrResolvable,
|
||||
"ip4_addr": isIP4AddrResolvable,
|
||||
"ip6_addr": isIP6AddrResolvable,
|
||||
"ip_addr": isIPAddrResolvable,
|
||||
"unix_addr": isUnixAddrResolvable,
|
||||
"mac": isMAC,
|
||||
"hostname": isHostnameRFC952, // RFC 952
|
||||
"hostname_rfc1123": isHostnameRFC1123, // RFC 1123
|
||||
"fqdn": isFQDN,
|
||||
"unique": isUnique,
|
||||
"oneof": isOneOf,
|
||||
"html": isHTML,
|
||||
"html_encoded": isHTMLEncoded,
|
||||
"url_encoded": isURLEncoded,
|
||||
"dir": isDir,
|
||||
"required": hasValue,
|
||||
"required_if": requiredIf,
|
||||
"required_unless": requiredUnless,
|
||||
"required_with": requiredWith,
|
||||
"required_with_all": requiredWithAll,
|
||||
"required_without": requiredWithout,
|
||||
"required_without_all": requiredWithoutAll,
|
||||
"excluded_with": excludedWith,
|
||||
"excluded_with_all": excludedWithAll,
|
||||
"excluded_without": excludedWithout,
|
||||
"excluded_without_all": excludedWithoutAll,
|
||||
"isdefault": isDefault,
|
||||
"len": hasLengthOf,
|
||||
"min": hasMinOf,
|
||||
"max": hasMaxOf,
|
||||
"eq": isEq,
|
||||
"ne": isNe,
|
||||
"lt": isLt,
|
||||
"lte": isLte,
|
||||
"gt": isGt,
|
||||
"gte": isGte,
|
||||
"eqfield": isEqField,
|
||||
"eqcsfield": isEqCrossStructField,
|
||||
"necsfield": isNeCrossStructField,
|
||||
"gtcsfield": isGtCrossStructField,
|
||||
"gtecsfield": isGteCrossStructField,
|
||||
"ltcsfield": isLtCrossStructField,
|
||||
"ltecsfield": isLteCrossStructField,
|
||||
"nefield": isNeField,
|
||||
"gtefield": isGteField,
|
||||
"gtfield": isGtField,
|
||||
"ltefield": isLteField,
|
||||
"ltfield": isLtField,
|
||||
"fieldcontains": fieldContains,
|
||||
"fieldexcludes": fieldExcludes,
|
||||
"alpha": isAlpha,
|
||||
"alphanum": isAlphanum,
|
||||
"alphaunicode": isAlphaUnicode,
|
||||
"alphanumunicode": isAlphanumUnicode,
|
||||
"numeric": isNumeric,
|
||||
"number": isNumber,
|
||||
"hexadecimal": isHexadecimal,
|
||||
"hexcolor": isHEXColor,
|
||||
"rgb": isRGB,
|
||||
"rgba": isRGBA,
|
||||
"hsl": isHSL,
|
||||
"hsla": isHSLA,
|
||||
"e164": isE164,
|
||||
"email": isEmail,
|
||||
"url": isURL,
|
||||
"uri": isURI,
|
||||
"urn_rfc2141": isUrnRFC2141, // RFC 2141
|
||||
"file": isFile,
|
||||
"base64": isBase64,
|
||||
"base64url": isBase64URL,
|
||||
"contains": contains,
|
||||
"containsany": containsAny,
|
||||
"containsrune": containsRune,
|
||||
"excludes": excludes,
|
||||
"excludesall": excludesAll,
|
||||
"excludesrune": excludesRune,
|
||||
"startswith": startsWith,
|
||||
"endswith": endsWith,
|
||||
"startsnotwith": startsNotWith,
|
||||
"endsnotwith": endsNotWith,
|
||||
"isbn": isISBN,
|
||||
"isbn10": isISBN10,
|
||||
"isbn13": isISBN13,
|
||||
"eth_addr": isEthereumAddress,
|
||||
"btc_addr": isBitcoinAddress,
|
||||
"btc_addr_bech32": isBitcoinBech32Address,
|
||||
"uuid": isUUID,
|
||||
"uuid3": isUUID3,
|
||||
"uuid4": isUUID4,
|
||||
"uuid5": isUUID5,
|
||||
"uuid_rfc4122": isUUIDRFC4122,
|
||||
"uuid3_rfc4122": isUUID3RFC4122,
|
||||
"uuid4_rfc4122": isUUID4RFC4122,
|
||||
"uuid5_rfc4122": isUUID5RFC4122,
|
||||
"ascii": isASCII,
|
||||
"printascii": isPrintableASCII,
|
||||
"multibyte": hasMultiByteCharacter,
|
||||
"datauri": isDataURI,
|
||||
"latitude": isLatitude,
|
||||
"longitude": isLongitude,
|
||||
"ssn": isSSN,
|
||||
"ipv4": isIPv4,
|
||||
"ipv6": isIPv6,
|
||||
"ip": isIP,
|
||||
"cidrv4": isCIDRv4,
|
||||
"cidrv6": isCIDRv6,
|
||||
"cidr": isCIDR,
|
||||
"tcp4_addr": isTCP4AddrResolvable,
|
||||
"tcp6_addr": isTCP6AddrResolvable,
|
||||
"tcp_addr": isTCPAddrResolvable,
|
||||
"udp4_addr": isUDP4AddrResolvable,
|
||||
"udp6_addr": isUDP6AddrResolvable,
|
||||
"udp_addr": isUDPAddrResolvable,
|
||||
"ip4_addr": isIP4AddrResolvable,
|
||||
"ip6_addr": isIP6AddrResolvable,
|
||||
"ip_addr": isIPAddrResolvable,
|
||||
"unix_addr": isUnixAddrResolvable,
|
||||
"mac": isMAC,
|
||||
"hostname": isHostnameRFC952, // RFC 952
|
||||
"hostname_rfc1123": isHostnameRFC1123, // RFC 1123
|
||||
"fqdn": isFQDN,
|
||||
"unique": isUnique,
|
||||
"oneof": isOneOf,
|
||||
"html": isHTML,
|
||||
"html_encoded": isHTMLEncoded,
|
||||
"url_encoded": isURLEncoded,
|
||||
"dir": isDir,
|
||||
"json": isJSON,
|
||||
"hostname_port": isHostnamePort,
|
||||
"lowercase": isLowercase,
|
||||
"uppercase": isUppercase,
|
||||
"datetime": isDatetime,
|
||||
"timezone": isTimeZone,
|
||||
"iso3166_1_alpha2": isIso3166Alpha2,
|
||||
"iso3166_1_alpha3": isIso3166Alpha3,
|
||||
"iso3166_1_alpha_numeric": isIso3166AlphaNumeric,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -177,7 +200,10 @@ func parseOneOfParam2(s string) []string {
|
||||
oneofValsCacheRWLock.RUnlock()
|
||||
if !ok {
|
||||
oneofValsCacheRWLock.Lock()
|
||||
vals = strings.Fields(s)
|
||||
vals = splitParamsRegex.FindAllString(s, -1)
|
||||
for i := 0; i < len(vals); i++ {
|
||||
vals[i] = strings.Replace(vals[i], "'", "", -1)
|
||||
}
|
||||
oneofValsCache[s] = vals
|
||||
oneofValsCacheRWLock.Unlock()
|
||||
}
|
||||
@@ -224,14 +250,38 @@ 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()))
|
||||
elem := field.Type().Elem()
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
elem = elem.Elem()
|
||||
}
|
||||
|
||||
if param == "" {
|
||||
m := reflect.MakeMap(reflect.MapOf(elem, v.Type()))
|
||||
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
m.SetMapIndex(reflect.Indirect(field.Index(i)), v)
|
||||
}
|
||||
return field.Len() == m.Len()
|
||||
}
|
||||
|
||||
sf, ok := elem.FieldByName(param)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Bad field name %s", param))
|
||||
}
|
||||
|
||||
sfTyp := sf.Type
|
||||
if sfTyp.Kind() == reflect.Ptr {
|
||||
sfTyp = sfTyp.Elem()
|
||||
}
|
||||
|
||||
m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
m.SetMapIndex(field.Index(i), v)
|
||||
m.SetMapIndex(reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param)), v)
|
||||
}
|
||||
return field.Len() == m.Len()
|
||||
case reflect.Map:
|
||||
@@ -489,7 +539,7 @@ func isISBN10(fl FieldLevel) bool {
|
||||
return checksum%11 == 0
|
||||
}
|
||||
|
||||
// IsEthereumAddress is the validation function for validating if the field's value is a valid ethereum address based currently only on the format
|
||||
// IsEthereumAddress is the validation function for validating if the field's value is a valid Ethereum address.
|
||||
func isEthereumAddress(fl FieldLevel) bool {
|
||||
address := fl.Field().String()
|
||||
|
||||
@@ -501,7 +551,21 @@ func isEthereumAddress(fl FieldLevel) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// checksum validation is blocked by https://github.com/golang/crypto/pull/28
|
||||
// Checksum validation. Reference: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
|
||||
address = address[2:] // Skip "0x" prefix.
|
||||
h := sha3.NewLegacyKeccak256()
|
||||
// hash.Hash's io.Writer implementation says it never returns an error. https://golang.org/pkg/hash/#Hash
|
||||
_, _ = h.Write([]byte(strings.ToLower(address)))
|
||||
hash := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
for i := 0; i < len(address); i++ {
|
||||
if address[i] <= '9' { // Skip 0-9 digits: they don't have upper/lower-case.
|
||||
continue
|
||||
}
|
||||
if hash[i] > '7' && address[i] >= 'a' || hash[i] <= '7' && address[i] <= 'F' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -666,6 +730,16 @@ func endsWith(fl FieldLevel) bool {
|
||||
return strings.HasSuffix(fl.Field().String(), fl.Param())
|
||||
}
|
||||
|
||||
// StartsNotWith is the validation function for validating that the field's value does not start with the text specified within the param.
|
||||
func startsNotWith(fl FieldLevel) bool {
|
||||
return !startsWith(fl)
|
||||
}
|
||||
|
||||
// EndsNotWith is the validation function for validating that the field's value does not end with the text specified within the param.
|
||||
func endsNotWith(fl FieldLevel) bool {
|
||||
return !endsWith(fl)
|
||||
}
|
||||
|
||||
// FieldContains is the validation function for validating if the current field's value contains the field specified by the param's value.
|
||||
func fieldContains(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
@@ -1093,7 +1167,7 @@ func isEq(fl FieldLevel) bool {
|
||||
return int64(field.Len()) == p
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p := asInt(param)
|
||||
p := asIntFromType(field.Type(), param)
|
||||
|
||||
return field.Int() == p
|
||||
|
||||
@@ -1106,6 +1180,11 @@ func isEq(fl FieldLevel) bool {
|
||||
p := asFloat(param)
|
||||
|
||||
return field.Float() == p
|
||||
|
||||
case reflect.Bool:
|
||||
p := asBool(param)
|
||||
|
||||
return field.Bool() == p
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
@@ -1178,7 +1257,7 @@ func isURL(fl FieldLevel) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return err == nil
|
||||
return true
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
@@ -1219,6 +1298,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 +1385,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 +1397,175 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
// requireCheckFieldValue is a func for check field value
|
||||
func requireCheckFieldValue(fl FieldLevel, param string, value string, defaultNotFoundValue bool) bool {
|
||||
field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
|
||||
if !found {
|
||||
return defaultNotFoundValue
|
||||
}
|
||||
|
||||
switch kind {
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return field.Int() == asInt(value)
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return field.Uint() == asUint(value)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return field.Float() == asFloat(value)
|
||||
|
||||
case reflect.Slice, reflect.Map, reflect.Array:
|
||||
return int64(field.Len()) == asInt(value)
|
||||
}
|
||||
|
||||
// default reflect.String:
|
||||
return field.String() == value
|
||||
}
|
||||
|
||||
// requiredIf is the validation function
|
||||
// The field under validation must be present and not empty only if all the other specified fields are equal to the value following with the specified field.
|
||||
func requiredIf(fl FieldLevel) bool {
|
||||
params := parseOneOfParam2(fl.Param())
|
||||
if len(params)%2 != 0 {
|
||||
panic(fmt.Sprintf("Bad param number for required_if %s", fl.FieldName()))
|
||||
}
|
||||
for i := 0; i < len(params); i += 2 {
|
||||
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return hasValue(fl)
|
||||
}
|
||||
|
||||
// requiredUnless is the validation function
|
||||
// The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field.
|
||||
func requiredUnless(fl FieldLevel) bool {
|
||||
params := parseOneOfParam2(fl.Param())
|
||||
if len(params)%2 != 0 {
|
||||
panic(fmt.Sprintf("Bad param number for required_unless %s", fl.FieldName()))
|
||||
}
|
||||
|
||||
for i := 0; i < len(params); i += 2 {
|
||||
if requireCheckFieldValue(fl, params[i], params[i+1], false) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return hasValue(fl)
|
||||
}
|
||||
|
||||
// ExcludedWith is the validation function
|
||||
// The field under validation must not be present or is empty if any of the other specified fields are present.
|
||||
func excludedWith(fl FieldLevel) bool {
|
||||
params := parseOneOfParam2(fl.Param())
|
||||
for _, param := range params {
|
||||
if !requireCheckFieldKind(fl, param, true) {
|
||||
return !hasValue(fl)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// ExcludedWithAll is the validation function
|
||||
// The field under validation must not be present or is empty if all of the other specified fields are present.
|
||||
func excludedWithAll(fl FieldLevel) bool {
|
||||
params := parseOneOfParam2(fl.Param())
|
||||
for _, param := range params {
|
||||
if requireCheckFieldKind(fl, param, true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return !hasValue(fl)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
return hasValue(fl)
|
||||
}
|
||||
|
||||
if isValidateCurrentField {
|
||||
return requireCheckFieldKind(fl, "")
|
||||
// ExcludedWithout is the validation function
|
||||
// The field under validation must not be present or is empty when any of the other specified fields are not present.
|
||||
func excludedWithout(fl FieldLevel) bool {
|
||||
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
|
||||
return !hasValue(fl)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
|
||||
return hasValue(fl)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
isValidateCurrentField := false
|
||||
// RequiredWithoutAll is the validation function
|
||||
// The field under validation must not be present or is empty when all of the other specified fields are not present.
|
||||
func excludedWithoutAll(fl FieldLevel) bool {
|
||||
params := parseOneOfParam2(fl.Param())
|
||||
for _, param := range params {
|
||||
|
||||
if requireCheckFieldKind(fl, param) {
|
||||
isValidateCurrentField = true
|
||||
if !requireCheckFieldKind(fl, param, true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if !isValidateCurrentField {
|
||||
return requireCheckFieldKind(fl, "")
|
||||
}
|
||||
|
||||
return true
|
||||
return !hasValue(fl)
|
||||
}
|
||||
|
||||
// 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.
|
||||
@@ -1513,7 +1681,7 @@ func isGte(fl FieldLevel) bool {
|
||||
return int64(field.Len()) >= p
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p := asInt(param)
|
||||
p := asIntFromType(field.Type(), param)
|
||||
|
||||
return field.Int() >= p
|
||||
|
||||
@@ -1560,7 +1728,7 @@ func isGt(fl FieldLevel) bool {
|
||||
return int64(field.Len()) > p
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p := asInt(param)
|
||||
p := asIntFromType(field.Type(), param)
|
||||
|
||||
return field.Int() > p
|
||||
|
||||
@@ -1603,7 +1771,7 @@ func hasLengthOf(fl FieldLevel) bool {
|
||||
return int64(field.Len()) == p
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p := asInt(param)
|
||||
p := asIntFromType(field.Type(), param)
|
||||
|
||||
return field.Int() == p
|
||||
|
||||
@@ -1739,7 +1907,7 @@ func isLte(fl FieldLevel) bool {
|
||||
return int64(field.Len()) <= p
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p := asInt(param)
|
||||
p := asIntFromType(field.Type(), param)
|
||||
|
||||
return field.Int() <= p
|
||||
|
||||
@@ -1786,7 +1954,7 @@ func isLt(fl FieldLevel) bool {
|
||||
return int64(field.Len()) < p
|
||||
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
p := asInt(param)
|
||||
p := asIntFromType(field.Type(), param)
|
||||
|
||||
return field.Int() < p
|
||||
|
||||
@@ -1974,12 +2142,7 @@ func isFQDN(fl FieldLevel) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if val[len(val)-1] == '.' {
|
||||
val = val[0 : len(val)-1]
|
||||
}
|
||||
|
||||
return strings.ContainsAny(val, ".") &&
|
||||
hostnameRegexRFC952.MatchString(val)
|
||||
return fqdnRegexRFC1123.MatchString(val)
|
||||
}
|
||||
|
||||
// IsDir is the validation function for validating if the current field's value is a valid directory.
|
||||
@@ -1997,3 +2160,126 @@ func isDir(fl FieldLevel) bool {
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
|
||||
// isJSON is the validation function for validating if the current field's value is a valid json string.
|
||||
func isJSON(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
if field.Kind() == reflect.String {
|
||||
val := field.String()
|
||||
return json.Valid([]byte(val))
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
|
||||
// isHostnamePort validates a <dns>:<port> combination for fields typically used for socket address.
|
||||
func isHostnamePort(fl FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
host, port, err := net.SplitHostPort(val)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
// Port must be a iny <= 65535.
|
||||
if portNum, err := strconv.ParseInt(port, 10, 32); err != nil || portNum > 65535 || portNum < 1 {
|
||||
return false
|
||||
}
|
||||
|
||||
// If host is specified, it should match a DNS name
|
||||
if host != "" {
|
||||
return hostnameRegexRFC1123.MatchString(host)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isLowercase is the validation function for validating if the current field's value is a lowercase string.
|
||||
func isLowercase(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
if field.Kind() == reflect.String {
|
||||
if field.String() == "" {
|
||||
return false
|
||||
}
|
||||
return field.String() == strings.ToLower(field.String())
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
|
||||
// isUppercase is the validation function for validating if the current field's value is an uppercase string.
|
||||
func isUppercase(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
if field.Kind() == reflect.String {
|
||||
if field.String() == "" {
|
||||
return false
|
||||
}
|
||||
return field.String() == strings.ToUpper(field.String())
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
|
||||
// isDatetime is the validation function for validating if the current field's value is a valid datetime string.
|
||||
func isDatetime(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
param := fl.Param()
|
||||
|
||||
if field.Kind() == reflect.String {
|
||||
_, err := time.Parse(param, field.String())
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
|
||||
// isTimeZone is the validation function for validating if the current field's value is a valid time zone string.
|
||||
func isTimeZone(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
if field.Kind() == reflect.String {
|
||||
// empty value is converted to UTC by time.LoadLocation but disallow it as it is not a valid time zone name
|
||||
if field.String() == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Local value is converted to the current system time zone by time.LoadLocation but disallow it as it is not a valid time zone name
|
||||
if strings.ToLower(field.String()) == "local" {
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := time.LoadLocation(field.String())
|
||||
return err == nil
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
|
||||
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 country code.
|
||||
func isIso3166Alpha2(fl FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
return iso3166_1_alpha2[val]
|
||||
}
|
||||
|
||||
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-3 country code.
|
||||
func isIso3166Alpha3(fl FieldLevel) bool {
|
||||
val := fl.Field().String()
|
||||
return iso3166_1_alpha3[val]
|
||||
}
|
||||
|
||||
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-numeric country code.
|
||||
func isIso3166AlphaNumeric(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
|
||||
var code int
|
||||
switch field.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
code = int(field.Int() % 1000)
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
code = int(field.Uint() % 1000)
|
||||
default:
|
||||
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
|
||||
}
|
||||
return iso3166_1_alpha_numeric[code]
|
||||
}
|
||||
Generated
Vendored
+18
-33
@@ -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)))
|
||||
}
|
||||
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package validator
|
||||
|
||||
var iso3166_1_alpha2 = map[string]bool{
|
||||
// see: https://www.iso.org/iso-3166-country-codes.html
|
||||
"AF": true, "AX": true, "AL": true, "DZ": true, "AS": true,
|
||||
"AD": true, "AO": true, "AI": true, "AQ": true, "AG": true,
|
||||
"AR": true, "AM": true, "AW": true, "AU": true, "AT": true,
|
||||
"AZ": true, "BS": true, "BH": true, "BD": true, "BB": true,
|
||||
"BY": true, "BE": true, "BZ": true, "BJ": true, "BM": true,
|
||||
"BT": true, "BO": true, "BQ": true, "BA": true, "BW": true,
|
||||
"BV": true, "BR": true, "IO": true, "BN": true, "BG": true,
|
||||
"BF": true, "BI": true, "KH": true, "CM": true, "CA": true,
|
||||
"CV": true, "KY": true, "CF": true, "TD": true, "CL": true,
|
||||
"CN": true, "CX": true, "CC": true, "CO": true, "KM": true,
|
||||
"CG": true, "CD": true, "CK": true, "CR": true, "CI": true,
|
||||
"HR": true, "CU": true, "CW": true, "CY": true, "CZ": true,
|
||||
"DK": true, "DJ": true, "DM": true, "DO": true, "EC": true,
|
||||
"EG": true, "SV": true, "GQ": true, "ER": true, "EE": true,
|
||||
"ET": true, "FK": true, "FO": true, "FJ": true, "FI": true,
|
||||
"FR": true, "GF": true, "PF": true, "TF": true, "GA": true,
|
||||
"GM": true, "GE": true, "DE": true, "GH": true, "GI": true,
|
||||
"GR": true, "GL": true, "GD": true, "GP": true, "GU": true,
|
||||
"GT": true, "GG": true, "GN": true, "GW": true, "GY": true,
|
||||
"HT": true, "HM": true, "VA": true, "HN": true, "HK": true,
|
||||
"HU": true, "IS": true, "IN": true, "ID": true, "IR": true,
|
||||
"IQ": true, "IE": true, "IM": true, "IL": true, "IT": true,
|
||||
"JM": true, "JP": true, "JE": true, "JO": true, "KZ": true,
|
||||
"KE": true, "KI": true, "KP": true, "KR": true, "KW": true,
|
||||
"KG": true, "LA": true, "LV": true, "LB": true, "LS": true,
|
||||
"LR": true, "LY": true, "LI": true, "LT": true, "LU": true,
|
||||
"MO": true, "MK": true, "MG": true, "MW": true, "MY": true,
|
||||
"MV": true, "ML": true, "MT": true, "MH": true, "MQ": true,
|
||||
"MR": true, "MU": true, "YT": true, "MX": true, "FM": true,
|
||||
"MD": true, "MC": true, "MN": true, "ME": true, "MS": true,
|
||||
"MA": true, "MZ": true, "MM": true, "NA": true, "NR": true,
|
||||
"NP": true, "NL": true, "NC": true, "NZ": true, "NI": true,
|
||||
"NE": true, "NG": true, "NU": true, "NF": true, "MP": true,
|
||||
"NO": true, "OM": true, "PK": true, "PW": true, "PS": true,
|
||||
"PA": true, "PG": true, "PY": true, "PE": true, "PH": true,
|
||||
"PN": true, "PL": true, "PT": true, "PR": true, "QA": true,
|
||||
"RE": true, "RO": true, "RU": true, "RW": true, "BL": true,
|
||||
"SH": true, "KN": true, "LC": true, "MF": true, "PM": true,
|
||||
"VC": true, "WS": true, "SM": true, "ST": true, "SA": true,
|
||||
"SN": true, "RS": true, "SC": true, "SL": true, "SG": true,
|
||||
"SX": true, "SK": true, "SI": true, "SB": true, "SO": true,
|
||||
"ZA": true, "GS": true, "SS": true, "ES": true, "LK": true,
|
||||
"SD": true, "SR": true, "SJ": true, "SZ": true, "SE": true,
|
||||
"CH": true, "SY": true, "TW": true, "TJ": true, "TZ": true,
|
||||
"TH": true, "TL": true, "TG": true, "TK": true, "TO": true,
|
||||
"TT": true, "TN": true, "TR": true, "TM": true, "TC": true,
|
||||
"TV": true, "UG": true, "UA": true, "AE": true, "GB": true,
|
||||
"US": true, "UM": true, "UY": true, "UZ": true, "VU": true,
|
||||
"VE": true, "VN": true, "VG": true, "VI": true, "WF": true,
|
||||
"EH": true, "YE": true, "ZM": true, "ZW": true,
|
||||
}
|
||||
|
||||
var iso3166_1_alpha3 = map[string]bool{
|
||||
// see: https://www.iso.org/iso-3166-country-codes.html
|
||||
"AFG": true, "ALB": true, "DZA": true, "ASM": true, "AND": true,
|
||||
"AGO": true, "AIA": true, "ATA": true, "ATG": true, "ARG": true,
|
||||
"ARM": true, "ABW": true, "AUS": true, "AUT": true, "AZE": true,
|
||||
"BHS": true, "BHR": true, "BGD": true, "BRB": true, "BLR": true,
|
||||
"BEL": true, "BLZ": true, "BEN": true, "BMU": true, "BTN": true,
|
||||
"BOL": true, "BES": true, "BIH": true, "BWA": true, "BVT": true,
|
||||
"BRA": true, "IOT": true, "BRN": true, "BGR": true, "BFA": true,
|
||||
"BDI": true, "CPV": true, "KHM": true, "CMR": true, "CAN": true,
|
||||
"CYM": true, "CAF": true, "TCD": true, "CHL": true, "CHN": true,
|
||||
"CXR": true, "CCK": true, "COL": true, "COM": true, "COD": true,
|
||||
"COG": true, "COK": true, "CRI": true, "HRV": true, "CUB": true,
|
||||
"CUW": true, "CYP": true, "CZE": true, "CIV": true, "DNK": true,
|
||||
"DJI": true, "DMA": true, "DOM": true, "ECU": true, "EGY": true,
|
||||
"SLV": true, "GNQ": true, "ERI": true, "EST": true, "SWZ": true,
|
||||
"ETH": true, "FLK": true, "FRO": true, "FJI": true, "FIN": true,
|
||||
"FRA": true, "GUF": true, "PYF": true, "ATF": true, "GAB": true,
|
||||
"GMB": true, "GEO": true, "DEU": true, "GHA": true, "GIB": true,
|
||||
"GRC": true, "GRL": true, "GRD": true, "GLP": true, "GUM": true,
|
||||
"GTM": true, "GGY": true, "GIN": true, "GNB": true, "GUY": true,
|
||||
"HTI": true, "HMD": true, "VAT": true, "HND": true, "HKG": true,
|
||||
"HUN": true, "ISL": true, "IND": true, "IDN": true, "IRN": true,
|
||||
"IRQ": true, "IRL": true, "IMN": true, "ISR": true, "ITA": true,
|
||||
"JAM": true, "JPN": true, "JEY": true, "JOR": true, "KAZ": true,
|
||||
"KEN": true, "KIR": true, "PRK": true, "KOR": true, "KWT": true,
|
||||
"KGZ": true, "LAO": true, "LVA": true, "LBN": true, "LSO": true,
|
||||
"LBR": true, "LBY": true, "LIE": true, "LTU": true, "LUX": true,
|
||||
"MAC": true, "MDG": true, "MWI": true, "MYS": true, "MDV": true,
|
||||
"MLI": true, "MLT": true, "MHL": true, "MTQ": true, "MRT": true,
|
||||
"MUS": true, "MYT": true, "MEX": true, "FSM": true, "MDA": true,
|
||||
"MCO": true, "MNG": true, "MNE": true, "MSR": true, "MAR": true,
|
||||
"MOZ": true, "MMR": true, "NAM": true, "NRU": true, "NPL": true,
|
||||
"NLD": true, "NCL": true, "NZL": true, "NIC": true, "NER": true,
|
||||
"NGA": true, "NIU": true, "NFK": true, "MKD": true, "MNP": true,
|
||||
"NOR": true, "OMN": true, "PAK": true, "PLW": true, "PSE": true,
|
||||
"PAN": true, "PNG": true, "PRY": true, "PER": true, "PHL": true,
|
||||
"PCN": true, "POL": true, "PRT": true, "PRI": true, "QAT": true,
|
||||
"ROU": true, "RUS": true, "RWA": true, "REU": true, "BLM": true,
|
||||
"SHN": true, "KNA": true, "LCA": true, "MAF": true, "SPM": true,
|
||||
"VCT": true, "WSM": true, "SMR": true, "STP": true, "SAU": true,
|
||||
"SEN": true, "SRB": true, "SYC": true, "SLE": true, "SGP": true,
|
||||
"SXM": true, "SVK": true, "SVN": true, "SLB": true, "SOM": true,
|
||||
"ZAF": true, "SGS": true, "SSD": true, "ESP": true, "LKA": true,
|
||||
"SDN": true, "SUR": true, "SJM": true, "SWE": true, "CHE": true,
|
||||
"SYR": true, "TWN": true, "TJK": true, "TZA": true, "THA": true,
|
||||
"TLS": true, "TGO": true, "TKL": true, "TON": true, "TTO": true,
|
||||
"TUN": true, "TUR": true, "TKM": true, "TCA": true, "TUV": true,
|
||||
"UGA": true, "UKR": true, "ARE": true, "GBR": true, "UMI": true,
|
||||
"USA": true, "URY": true, "UZB": true, "VUT": true, "VEN": true,
|
||||
"VNM": true, "VGB": true, "VIR": true, "WLF": true, "ESH": true,
|
||||
"YEM": true, "ZMB": true, "ZWE": true, "ALA": true,
|
||||
}
|
||||
var iso3166_1_alpha_numeric = map[int]bool{
|
||||
// see: https://www.iso.org/iso-3166-country-codes.html
|
||||
4: true, 8: true, 12: true, 16: true, 20: true,
|
||||
24: true, 660: true, 10: true, 28: true, 32: true,
|
||||
51: true, 533: true, 36: true, 40: true, 31: true,
|
||||
44: true, 48: true, 50: true, 52: true, 112: true,
|
||||
56: true, 84: true, 204: true, 60: true, 64: true,
|
||||
68: true, 535: true, 70: true, 72: true, 74: true,
|
||||
76: true, 86: true, 96: true, 100: true, 854: true,
|
||||
108: true, 132: true, 116: true, 120: true, 124: true,
|
||||
136: true, 140: true, 148: true, 152: true, 156: true,
|
||||
162: true, 166: true, 170: true, 174: true, 180: true,
|
||||
178: true, 184: true, 188: true, 191: true, 192: true,
|
||||
531: true, 196: true, 203: true, 384: true, 208: true,
|
||||
262: true, 212: true, 214: true, 218: true, 818: true,
|
||||
222: true, 226: true, 232: true, 233: true, 748: true,
|
||||
231: true, 238: true, 234: true, 242: true, 246: true,
|
||||
250: true, 254: true, 258: true, 260: true, 266: true,
|
||||
270: true, 268: true, 276: true, 288: true, 292: true,
|
||||
300: true, 304: true, 308: true, 312: true, 316: true,
|
||||
320: true, 831: true, 324: true, 624: true, 328: true,
|
||||
332: true, 334: true, 336: true, 340: true, 344: true,
|
||||
348: true, 352: true, 356: true, 360: true, 364: true,
|
||||
368: true, 372: true, 833: true, 376: true, 380: true,
|
||||
388: true, 392: true, 832: true, 400: true, 398: true,
|
||||
404: true, 296: true, 408: true, 410: true, 414: true,
|
||||
417: true, 418: true, 428: true, 422: true, 426: true,
|
||||
430: true, 434: true, 438: true, 440: true, 442: true,
|
||||
446: true, 450: true, 454: true, 458: true, 462: true,
|
||||
466: true, 470: true, 584: true, 474: true, 478: true,
|
||||
480: true, 175: true, 484: true, 583: true, 498: true,
|
||||
492: true, 496: true, 499: true, 500: true, 504: true,
|
||||
508: true, 104: true, 516: true, 520: true, 524: true,
|
||||
528: true, 540: true, 554: true, 558: true, 562: true,
|
||||
566: true, 570: true, 574: true, 807: true, 580: true,
|
||||
578: true, 512: true, 586: true, 585: true, 275: true,
|
||||
591: true, 598: true, 600: true, 604: true, 608: true,
|
||||
612: true, 616: true, 620: true, 630: true, 634: true,
|
||||
642: true, 643: true, 646: true, 638: true, 652: true,
|
||||
654: true, 659: true, 662: true, 663: true, 666: true,
|
||||
670: true, 882: true, 674: true, 678: true, 682: true,
|
||||
686: true, 688: true, 690: true, 694: true, 702: true,
|
||||
534: true, 703: true, 705: true, 90: true, 706: true,
|
||||
710: true, 239: true, 728: true, 724: true, 144: true,
|
||||
729: true, 740: true, 744: true, 752: true, 756: true,
|
||||
760: true, 158: true, 762: true, 834: true, 764: true,
|
||||
626: true, 768: true, 772: true, 776: true, 780: true,
|
||||
788: true, 792: true, 795: true, 796: true, 798: true,
|
||||
800: true, 804: true, 784: true, 826: true, 581: true,
|
||||
840: true, 858: true, 860: true, 548: true, 862: true,
|
||||
704: true, 92: true, 850: true, 876: true, 732: true,
|
||||
887: true, 894: true, 716: true, 248: true,
|
||||
}
|
||||
Generated
Vendored
+252
-45
@@ -5,7 +5,7 @@ based on tags.
|
||||
It can also handle Cross-Field and Cross-Struct validation for nested structs
|
||||
and has the ability to dive into arrays and maps of any type.
|
||||
|
||||
see more examples https://github.com/go-playground/validator/tree/v9/_examples
|
||||
see more examples https://github.com/go-playground/validator/tree/master/_examples
|
||||
|
||||
Validation Functions Return Type error
|
||||
|
||||
@@ -158,7 +158,7 @@ handy in ignoring embedded structs from being validated. (Usage: -)
|
||||
Or Operator
|
||||
|
||||
This is the 'or' operator allowing multiple validators to be used and
|
||||
accepted. (Usage: rbg|rgba) <-- this would allow either rgb or rgba
|
||||
accepted. (Usage: rgb|rgba) <-- this would allow either rgb or rgba
|
||||
colors to be accepted. This can also be combined with 'and' for example
|
||||
( Usage: omitempty,rgb|rgba)
|
||||
|
||||
@@ -245,6 +245,40 @@ ensures the value is not nil.
|
||||
|
||||
Usage: required
|
||||
|
||||
Required If
|
||||
|
||||
The field under validation must be present and not empty only if all
|
||||
the other specified fields are equal to the value following the specified
|
||||
field. For strings ensures value is not "". For slices, maps, pointers,
|
||||
interfaces, channels and functions ensures the value is not nil.
|
||||
|
||||
Usage: required_if
|
||||
|
||||
Examples:
|
||||
|
||||
// require the field if the Field1 is equal to the parameter given:
|
||||
Usage: required_if=Field1 foobar
|
||||
|
||||
// require the field if the Field1 and Field2 is equal to the value respectively:
|
||||
Usage: required_if=Field1 foo Field2 bar
|
||||
|
||||
Required Unless
|
||||
|
||||
The field under validation must be present and not empty unless all
|
||||
the other specified fields are equal to the value following the specified
|
||||
field. For strings ensures value is not "". For slices, maps, pointers,
|
||||
interfaces, channels and functions ensures the value is not nil.
|
||||
|
||||
Usage: required_unless
|
||||
|
||||
Examples:
|
||||
|
||||
// require the field unless the Field1 is equal to the parameter given:
|
||||
Usage: required_unless=Field1 foobar
|
||||
|
||||
// require the field unless the Field1 and Field2 is equal to the value respectively:
|
||||
Usage: required_unless=Field1 foo Field2 bar
|
||||
|
||||
Required With
|
||||
|
||||
The field under validation must be present and not empty only if any
|
||||
@@ -321,8 +355,17 @@ equal to the parameter given. For strings, it checks that
|
||||
the string length is exactly that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: len=10
|
||||
|
||||
Example #2 (time.Duration)
|
||||
|
||||
For time.Duration, len will ensure that the value is equal to the duration given
|
||||
in the parameter.
|
||||
|
||||
Usage: len=1h30m
|
||||
|
||||
Maximum
|
||||
|
||||
For numbers, max will ensure that the value is
|
||||
@@ -330,8 +373,17 @@ less than or equal to the parameter given. For strings, it checks
|
||||
that the string length is at most that number of characters. For
|
||||
slices, arrays, and maps, validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: max=10
|
||||
|
||||
Example #2 (time.Duration)
|
||||
|
||||
For time.Duration, max will ensure that the value is less than or equal to the
|
||||
duration given in the parameter.
|
||||
|
||||
Usage: max=1h30m
|
||||
|
||||
Minimum
|
||||
|
||||
For numbers, min will ensure that the value is
|
||||
@@ -339,32 +391,61 @@ greater or equal to the parameter given. For strings, it checks that
|
||||
the string length is at least that number of characters. For slices,
|
||||
arrays, and maps, validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: min=10
|
||||
|
||||
Example #2 (time.Duration)
|
||||
|
||||
For time.Duration, min will ensure that the value is greater than or equal to
|
||||
the duration given in the parameter.
|
||||
|
||||
Usage: min=1h30m
|
||||
|
||||
Equals
|
||||
|
||||
For strings & numbers, eq will ensure that the value is
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: eq=10
|
||||
|
||||
Example #2 (time.Duration)
|
||||
|
||||
For time.Duration, eq will ensure that the value is equal to the duration given
|
||||
in the parameter.
|
||||
|
||||
Usage: eq=1h30m
|
||||
|
||||
Not Equal
|
||||
|
||||
For strings & numbers, ne will ensure that the value is not
|
||||
equal to the parameter given. For slices, arrays, and maps,
|
||||
validates the number of items.
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: ne=10
|
||||
|
||||
Example #2 (time.Duration)
|
||||
|
||||
For time.Duration, ne will ensure that the value is not equal to the duration
|
||||
given in the parameter.
|
||||
|
||||
Usage: ne=1h30m
|
||||
|
||||
One Of
|
||||
|
||||
For strings, ints, and uints, oneof will ensure that the value
|
||||
is one of the values in the parameter. The parameter should be
|
||||
a list of values separated by whitespace. Values may be
|
||||
strings or numbers.
|
||||
a list of values separated by whitespace. Values may be
|
||||
strings or numbers. To match strings with spaces in them, include
|
||||
the target string between single quotes.
|
||||
|
||||
Usage: oneof=red green
|
||||
oneof='red green' 'blue yellow'
|
||||
oneof=5 7 9
|
||||
|
||||
Greater Than
|
||||
@@ -384,11 +465,17 @@ For time.Time ensures the time value is greater than time.Now.UTC().
|
||||
|
||||
Usage: gt
|
||||
|
||||
Example #3 (time.Duration)
|
||||
|
||||
For time.Duration, gt will ensure that the value is greater than the duration
|
||||
given in the parameter.
|
||||
|
||||
Usage: gt=1h30m
|
||||
|
||||
Greater Than or Equal
|
||||
|
||||
Same as 'min' above. Kept both to make terminology with 'len' easier.
|
||||
|
||||
|
||||
Example #1
|
||||
|
||||
Usage: gte=10
|
||||
@@ -399,6 +486,13 @@ For time.Time ensures the time value is greater than or equal to time.Now.UTC().
|
||||
|
||||
Usage: gte
|
||||
|
||||
Example #3 (time.Duration)
|
||||
|
||||
For time.Duration, gte will ensure that the value is greater than or equal to
|
||||
the duration given in the parameter.
|
||||
|
||||
Usage: gte=1h30m
|
||||
|
||||
Less Than
|
||||
|
||||
For numbers, this will ensure that the value is less than the parameter given.
|
||||
@@ -410,10 +504,18 @@ Example #1
|
||||
Usage: lt=10
|
||||
|
||||
Example #2 (time.Time)
|
||||
|
||||
For time.Time ensures the time value is less than time.Now.UTC().
|
||||
|
||||
Usage: lt
|
||||
|
||||
Example #3 (time.Duration)
|
||||
|
||||
For time.Duration, lt will ensure that the value is less than the duration given
|
||||
in the parameter.
|
||||
|
||||
Usage: lt=1h30m
|
||||
|
||||
Less Than or Equal
|
||||
|
||||
Same as 'max' above. Kept both to make terminology with 'len' easier.
|
||||
@@ -428,6 +530,13 @@ For time.Time ensures the time value is less than or equal to time.Now.UTC().
|
||||
|
||||
Usage: lte
|
||||
|
||||
Example #3 (time.Duration)
|
||||
|
||||
For time.Duration, lte will ensure that the value is less than or equal to the
|
||||
duration given in the parameter.
|
||||
|
||||
Usage: lte=1h30m
|
||||
|
||||
Field Equals Another Field
|
||||
|
||||
This will validate the field value against another fields value either within
|
||||
@@ -474,9 +583,9 @@ relative to the top level struct.
|
||||
|
||||
Field Greater Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
Only valid for Numbers, time.Duration and time.Time types, this will validate
|
||||
the field value against another fields value either within a struct or passed in
|
||||
field. usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
@@ -488,7 +597,6 @@ Example #2:
|
||||
// Validating by field:
|
||||
validate.VarWithValue(start, end, "gtfield")
|
||||
|
||||
|
||||
Field Greater Than Another Relative Field
|
||||
|
||||
This does the same as gtfield except that it validates the field provided
|
||||
@@ -498,9 +606,9 @@ relative to the top level struct.
|
||||
|
||||
Field Greater Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
Only valid for Numbers, time.Duration and time.Time types, this will validate
|
||||
the field value against another fields value either within a struct or passed in
|
||||
field. usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
@@ -521,9 +629,9 @@ to the top level struct.
|
||||
|
||||
Less Than Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
Only valid for Numbers, time.Duration and time.Time types, this will validate
|
||||
the field value against another fields value either within a struct or passed in
|
||||
field. usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
@@ -544,9 +652,9 @@ to the top level struct.
|
||||
|
||||
Less Than or Equal To Another Field
|
||||
|
||||
Only valid for Numbers and time.Time types, this will validate the field value
|
||||
against another fields value either within a struct or passed in field.
|
||||
usage examples are for validation of a Start and End date:
|
||||
Only valid for Numbers, time.Duration and time.Time types, this will validate
|
||||
the field value against another fields value either within a struct or passed in
|
||||
field. usage examples are for validation of a Start and End date:
|
||||
|
||||
Example #1:
|
||||
|
||||
@@ -585,9 +693,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
|
||||
@@ -612,6 +726,13 @@ This validates that a string value contains unicode alphanumeric characters only
|
||||
|
||||
Usage: alphanumunicode
|
||||
|
||||
Number
|
||||
|
||||
This validates that a string value contains number values only.
|
||||
For integers or float it returns true.
|
||||
|
||||
Usage: number
|
||||
|
||||
Numeric
|
||||
|
||||
This validates that a string value contains a basic numeric value.
|
||||
@@ -633,6 +754,18 @@ hashtag (#)
|
||||
|
||||
Usage: hexcolor
|
||||
|
||||
Lowercase String
|
||||
|
||||
This validates that a string value contains only lowercase characters. An empty string is not a valid lowercase string.
|
||||
|
||||
Usage: lowercase
|
||||
|
||||
Uppercase String
|
||||
|
||||
This validates that a string value contains only uppercase characters. An empty string is not a valid uppercase string.
|
||||
|
||||
Usage: uppercase
|
||||
|
||||
RGB String
|
||||
|
||||
This validates that a string value contains a valid rgb color
|
||||
@@ -657,6 +790,13 @@ This validates that a string value contains a valid hsla color
|
||||
|
||||
Usage: hsla
|
||||
|
||||
E.164 Phone Number String
|
||||
|
||||
This validates that a string value contains a valid E.164 Phone number
|
||||
https://en.wikipedia.org/wiki/E.164 (ex. +1123456789)
|
||||
|
||||
Usage: e164
|
||||
|
||||
E-mail String
|
||||
|
||||
This validates that a string value contains a valid email
|
||||
@@ -665,6 +805,12 @@ does any email provider accept all possibilities.
|
||||
|
||||
Usage: email
|
||||
|
||||
JSON String
|
||||
|
||||
This validates that a string value is valid JSON
|
||||
|
||||
Usage: json
|
||||
|
||||
File path
|
||||
|
||||
This validates that a string value contains a valid file path and that
|
||||
@@ -733,8 +879,7 @@ Special thanks to Pieter Wuille for providng reference implementations.
|
||||
Ethereum Address
|
||||
|
||||
This validates that a string value contains a valid ethereum address.
|
||||
The format of the string is checked to ensure it matches the standard Ethereum address format
|
||||
Full validation is blocked by https://github.com/golang/crypto/pull/28
|
||||
The format of the string is checked to ensure it matches the standard Ethereum address format.
|
||||
|
||||
Usage: eth_addr
|
||||
|
||||
@@ -788,6 +933,18 @@ This validates that a string value ends with the supplied string value
|
||||
|
||||
Usage: endswith=goodbye
|
||||
|
||||
Does Not Start With
|
||||
|
||||
This validates that a string value does not start with the supplied string value
|
||||
|
||||
Usage: startsnotwith=hello
|
||||
|
||||
Does Not End With
|
||||
|
||||
This validates that a string value does not end with the supplied string value
|
||||
|
||||
Usage: endsnotwith=goodbye
|
||||
|
||||
International Standard Book Number
|
||||
|
||||
This validates that a string value contains a valid isbn10 or isbn13 value.
|
||||
@@ -1029,6 +1186,50 @@ This is done using os.Stat, which is a platform independent function.
|
||||
|
||||
Usage: dir
|
||||
|
||||
HostPort
|
||||
|
||||
This validates that a string value contains a valid DNS hostname and port that
|
||||
can be used to valiate fields typically passed to sockets and connections.
|
||||
|
||||
Usage: hostname_port
|
||||
|
||||
Datetime
|
||||
|
||||
This validates that a string value is a valid datetime based on the supplied datetime format.
|
||||
Supplied format must match the official Go time format layout as documented in https://golang.org/pkg/time/
|
||||
|
||||
Usage: datetime=2006-01-02
|
||||
|
||||
Iso3166-1 alpha-2
|
||||
|
||||
This validates that a string value is a valid country code based on iso3166-1 alpha-2 standard.
|
||||
see: https://www.iso.org/iso-3166-country-codes.html
|
||||
|
||||
Usage: iso3166_1_alpha2
|
||||
|
||||
Iso3166-1 alpha-3
|
||||
|
||||
This validates that a string value is a valid country code based on iso3166-1 alpha-3 standard.
|
||||
see: https://www.iso.org/iso-3166-country-codes.html
|
||||
|
||||
Usage: iso3166_1_alpha3
|
||||
|
||||
Iso3166-1 alpha-numeric
|
||||
|
||||
This validates that a string value is a valid country code based on iso3166-1 alpha-numeric standard.
|
||||
see: https://www.iso.org/iso-3166-country-codes.html
|
||||
|
||||
Usage: iso3166_1_alpha3
|
||||
|
||||
TimeZone
|
||||
|
||||
This validates that a string value is a valid time zone based on the time zone database present on the system.
|
||||
Although empty value and Local value are allowed by time.LoadLocation golang function, they are not allowed by this validator.
|
||||
More information on https://golang.org/pkg/time/#LoadLocation
|
||||
|
||||
Usage: timezone
|
||||
|
||||
|
||||
Alias Validators and Tags
|
||||
|
||||
NOTE: When returning an error, the tag returned in "FieldError" will be
|
||||
@@ -1040,6 +1241,8 @@ Here is a list of the current built in alias tags:
|
||||
|
||||
"iscolor"
|
||||
alias is "hexcolor|rgb|rgba|hsl|hsla" (Usage: iscolor)
|
||||
"country_code"
|
||||
alias is "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric" (Usage: country_code)
|
||||
|
||||
Validator notes:
|
||||
|
||||
@@ -1058,6 +1261,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 +1304,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
|
||||
Generated
Vendored
+17
-14
@@ -99,7 +99,7 @@ type FieldError interface {
|
||||
ActualTag() string
|
||||
|
||||
// returns the namespace for the field error, with the tag
|
||||
// name taking precedence over the fields actual name.
|
||||
// name taking precedence over the field's actual name.
|
||||
//
|
||||
// eg. JSON name "User.fname"
|
||||
//
|
||||
@@ -109,29 +109,29 @@ type FieldError interface {
|
||||
// using validate.Field(...) as there is no way to extract it's name
|
||||
Namespace() string
|
||||
|
||||
// returns the namespace for the field error, with the fields
|
||||
// returns the namespace for the field error, with the field's
|
||||
// actual name.
|
||||
//
|
||||
// eq. "User.FirstName" see Namespace for comparison
|
||||
//
|
||||
// NOTE: this field can be blank when validating a single primitive field
|
||||
// using validate.Field(...) as there is no way to extract it's name
|
||||
// using validate.Field(...) as there is no way to extract its name
|
||||
StructNamespace() string
|
||||
|
||||
// returns the fields name with the tag name taking precedence over the
|
||||
// fields actual name.
|
||||
// field's actual name.
|
||||
//
|
||||
// eq. JSON name "fname"
|
||||
// see StructField for comparison
|
||||
Field() string
|
||||
|
||||
// returns the fields actual name from the struct, when able to determine.
|
||||
// returns the field's actual name from the struct, when able to determine.
|
||||
//
|
||||
// eq. "FirstName"
|
||||
// see Field for comparison
|
||||
StructField() string
|
||||
|
||||
// returns the actual fields value in case needed for creating the error
|
||||
// returns the actual field's value in case needed for creating the error
|
||||
// message
|
||||
Value() interface{}
|
||||
|
||||
@@ -155,6 +155,9 @@ type FieldError interface {
|
||||
// NOTE: if no registered translator can be found it returns the same as
|
||||
// calling fe.Error()
|
||||
Translate(ut ut.Translator) string
|
||||
|
||||
// Error returns the FieldError's message
|
||||
Error() string
|
||||
}
|
||||
|
||||
// compile time interface checks
|
||||
@@ -190,19 +193,19 @@ func (fe *fieldError) ActualTag() string {
|
||||
}
|
||||
|
||||
// Namespace returns the namespace for the field error, with the tag
|
||||
// name taking precedence over the fields actual name.
|
||||
// name taking precedence over the field's actual name.
|
||||
func (fe *fieldError) Namespace() string {
|
||||
return fe.ns
|
||||
}
|
||||
|
||||
// StructNamespace returns the namespace for the field error, with the fields
|
||||
// StructNamespace returns the namespace for the field error, with the field's
|
||||
// actual name.
|
||||
func (fe *fieldError) StructNamespace() string {
|
||||
return fe.structNs
|
||||
}
|
||||
|
||||
// Field returns the fields name with the tag name taking precedence over the
|
||||
// fields actual name.
|
||||
// Field returns the field's name with the tag name taking precedence over the
|
||||
// field's actual name.
|
||||
func (fe *fieldError) Field() string {
|
||||
|
||||
return fe.ns[len(fe.ns)-int(fe.fieldLen):]
|
||||
@@ -218,13 +221,13 @@ func (fe *fieldError) Field() string {
|
||||
// return fld
|
||||
}
|
||||
|
||||
// returns the fields actual name from the struct, when able to determine.
|
||||
// returns the field's actual name from the struct, when able to determine.
|
||||
func (fe *fieldError) StructField() string {
|
||||
// return fe.structField
|
||||
return fe.structNs[len(fe.structNs)-int(fe.structfieldLen):]
|
||||
}
|
||||
|
||||
// Value returns the actual fields value in case needed for creating the error
|
||||
// Value returns the actual field's value in case needed for creating the error
|
||||
// message
|
||||
func (fe *fieldError) Value() interface{} {
|
||||
return fe.value
|
||||
@@ -254,8 +257,8 @@ func (fe *fieldError) Error() string {
|
||||
// Translate returns the FieldError's translated error
|
||||
// from the provided 'ut.Translator' and registered 'TranslationFunc'
|
||||
//
|
||||
// NOTE: is not registered translation can be found it returns the same
|
||||
// as calling fe.Error()
|
||||
// NOTE: if no registered translation can be found, it returns the original
|
||||
// untranslated error message.
|
||||
func (fe *fieldError) Translate(ut ut.Translator) string {
|
||||
|
||||
m, ok := fe.v.transTagFunc[ut]
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package validator
|
||||
|
||||
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
|
||||
|
||||
// returns the current fields parent struct, if any or
|
||||
// the comparison value if called 'VarWithValue'
|
||||
Parent() reflect.Value
|
||||
|
||||
// returns current field for validation
|
||||
Field() reflect.Value
|
||||
|
||||
// returns the field's name with the tag
|
||||
// name taking precedence over the fields actual name.
|
||||
FieldName() string
|
||||
|
||||
// returns the struct field's name
|
||||
StructFieldName() string
|
||||
|
||||
// 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.
|
||||
ExtractType(field reflect.Value) (value reflect.Value, kind reflect.Kind, nullable bool)
|
||||
|
||||
// traverses the parent struct to retrieve a specific field denoted by the provided namespace
|
||||
// in the param and returns the field, field kind 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.
|
||||
//
|
||||
// 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)
|
||||
|
||||
// Field returns current field for validation
|
||||
func (v *validate) Field() reflect.Value {
|
||||
return v.flField
|
||||
}
|
||||
|
||||
// FieldName returns the field's name with the tag
|
||||
// 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
|
||||
}
|
||||
|
||||
// Param returns param for validation against current field
|
||||
func (v *validate) Param() string {
|
||||
return v.ct.param
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
module github.com/go-playground/validator/v10
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/go-playground/assert/v2 v2.0.1
|
||||
github.com/go-playground/locales v0.13.0
|
||||
github.com/go-playground/universal-translator v0.17.0
|
||||
github.com/leodido/go-urn v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
)
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
Generated
Vendored
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Generated
Vendored
+13
-7
@@ -9,13 +9,14 @@ const (
|
||||
alphaUnicodeNumericRegexString = "^[\\p{L}\\p{N}]+$"
|
||||
numericRegexString = "^[-+]?[0-9]+(?:\\.[0-9]+)?$"
|
||||
numberRegexString = "^[0-9]+$"
|
||||
hexadecimalRegexString = "^[0-9a-fA-F]+$"
|
||||
hexadecimalRegexString = "^(0[xX])?[0-9a-fA-F]+$"
|
||||
hexcolorRegexString = "^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$"
|
||||
rgbRegexString = "^rgb\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*\\)$"
|
||||
rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
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})$"
|
||||
@@ -31,21 +32,23 @@ const (
|
||||
aSCIIRegexString = "^[\x00-\x7F]*$"
|
||||
printableASCIIRegexString = "^[\x20-\x7E]*$"
|
||||
multibyteRegexString = "[^\x00-\x7F]"
|
||||
dataURIRegexString = "^data:.+\\/(.+);base64$"
|
||||
dataURIRegexString = `^data:((?:\w+\/(?:([^;]|;[^;]).)+)?)`
|
||||
latitudeRegexString = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$"
|
||||
longitudeRegexString = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$"
|
||||
sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$`
|
||||
hostnameRegexStringRFC952 = `^[a-zA-Z][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952
|
||||
hostnameRegexStringRFC1123 = `^[a-zA-Z0-9][a-zA-Z0-9\-\.]+[a-zA-Z0-9]$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123
|
||||
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
|
||||
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952
|
||||
hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123
|
||||
fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62})(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.')
|
||||
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
|
||||
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
ethAddressRegexString = `^0x[0-9a-fA-F]{40}$`
|
||||
ethAddressUpperRegexString = `^0x[0-9A-F]{40}$`
|
||||
ethAddressLowerRegexString = `^0x[0-9a-f]{40}$`
|
||||
uRLEncodedRegexString = `(%[A-Fa-f0-9]{2})`
|
||||
hTMLEncodedRegexString = `&#[x]?([0-9a-fA-F]{2})|(>)|(<)|(")|(&)+[;]?`
|
||||
hTMLRegexString = `<[/]?([a-zA-Z]+).*?>`
|
||||
splitParamsRegexString = `'[^']*'|\S+`
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -61,6 +64,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)
|
||||
@@ -83,6 +87,7 @@ var (
|
||||
sSNRegex = regexp.MustCompile(sSNRegexString)
|
||||
hostnameRegexRFC952 = regexp.MustCompile(hostnameRegexStringRFC952)
|
||||
hostnameRegexRFC1123 = regexp.MustCompile(hostnameRegexStringRFC1123)
|
||||
fqdnRegexRFC1123 = regexp.MustCompile(fqdnRegexStringRFC1123)
|
||||
btcAddressRegex = regexp.MustCompile(btcAddressRegexString)
|
||||
btcUpperAddressRegexBech32 = regexp.MustCompile(btcAddressUpperRegexStringBech32)
|
||||
btcLowerAddressRegexBech32 = regexp.MustCompile(btcAddressLowerRegexStringBech32)
|
||||
@@ -92,4 +97,5 @@ var (
|
||||
uRLEncodedRegex = regexp.MustCompile(uRLEncodedRegexString)
|
||||
hTMLEncodedRegex = regexp.MustCompile(hTMLEncodedRegexString)
|
||||
hTMLRegex = regexp.MustCompile(hTMLRegexString)
|
||||
splitParamsRegex = regexp.MustCompile(splitParamsRegexString)
|
||||
)
|
||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
+36
-5
@@ -4,6 +4,7 @@ import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// extractTypeInternal gets the actual underlying type of field value.
|
||||
@@ -57,11 +58,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 +112,7 @@ BEGIN:
|
||||
arrIdx, _ := strconv.Atoi(namespace[idx+1 : idx2])
|
||||
|
||||
if arrIdx >= current.Len() {
|
||||
return current, kind, false
|
||||
return
|
||||
}
|
||||
|
||||
startIdx := idx2 + 1
|
||||
@@ -223,13 +223,34 @@ BEGIN:
|
||||
// asInt returns the parameter as a int64
|
||||
// or panics if it can't convert
|
||||
func asInt(param string) int64 {
|
||||
|
||||
i, err := strconv.ParseInt(param, 0, 64)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// asIntFromTimeDuration parses param as time.Duration and returns it as int64
|
||||
// or panics on error.
|
||||
func asIntFromTimeDuration(param string) int64 {
|
||||
d, err := time.ParseDuration(param)
|
||||
if err != nil {
|
||||
// attempt parsing as an an integer assuming nanosecond precision
|
||||
return asInt(param)
|
||||
}
|
||||
return int64(d)
|
||||
}
|
||||
|
||||
// asIntFromType calls the proper function to parse param as int64,
|
||||
// given a field's Type t.
|
||||
func asIntFromType(t reflect.Type, param string) int64 {
|
||||
switch t {
|
||||
case timeDurationType:
|
||||
return asIntFromTimeDuration(param)
|
||||
default:
|
||||
return asInt(param)
|
||||
}
|
||||
}
|
||||
|
||||
// asUint returns the parameter as a uint64
|
||||
// or panics if it can't convert
|
||||
func asUint(param string) uint64 {
|
||||
@@ -250,6 +271,16 @@ func asFloat(param string) float64 {
|
||||
return i
|
||||
}
|
||||
|
||||
// asBool returns the parameter as a bool
|
||||
// or panics if it can't convert
|
||||
func asBool(param string) bool {
|
||||
|
||||
i, err := strconv.ParseBool(param)
|
||||
panicIf(err)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
func panicIf(err error) {
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
Generated
Vendored
+32
-30
@@ -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:
|
||||
@@ -247,7 +249,7 @@ OUTER:
|
||||
v.cf = cf
|
||||
v.ct = ct
|
||||
|
||||
if !v.fldIsPointer && !hasValue(v) {
|
||||
if !hasValue(v) {
|
||||
return
|
||||
}
|
||||
|
||||
Generated
Vendored
+55
-36
@@ -13,31 +13,39 @@ 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"
|
||||
requiredIfTag = "required_if"
|
||||
requiredUnlessTag = "required_unless"
|
||||
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 (
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
timeDurationType = reflect.TypeOf(time.Duration(0))
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
|
||||
defaultCField = &cField{namesEqual: true}
|
||||
)
|
||||
|
||||
@@ -55,6 +63,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 +78,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 +96,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 +109,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 requiredIfTag, requiredUnlessTag, 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 +159,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 +183,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
|
||||
}
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- go: 1.7.x
|
||||
- go: 1.8.x
|
||||
- go: 1.9.x
|
||||
- go: 1.10.x
|
||||
- go: 1.11.x
|
||||
- go: tip
|
||||
allow_failures:
|
||||
- go: tip
|
||||
|
||||
script:
|
||||
- go get -t -v ./...
|
||||
- diff -u <(echo -n) <(gofmt -d .)
|
||||
- go vet $(go list ./... | grep -v /vendor/)
|
||||
- go test -v -race ./...
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
# Gorilla WebSocket
|
||||
|
||||
[](https://godoc.org/github.com/gorilla/websocket)
|
||||
[](https://circleci.com/gh/gorilla/websocket)
|
||||
|
||||
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
|
||||
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
|
||||
|
||||
[](https://travis-ci.org/gorilla/websocket)
|
||||
[](https://godoc.org/github.com/gorilla/websocket)
|
||||
|
||||
### Documentation
|
||||
|
||||
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
|
||||
@@ -27,7 +27,7 @@ package API is stable.
|
||||
### Protocol Compliance
|
||||
|
||||
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
|
||||
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
|
||||
Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn
|
||||
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
|
||||
|
||||
### Gorilla WebSocket compared with other packages
|
||||
@@ -40,7 +40,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn
|
||||
</tr>
|
||||
<tr>
|
||||
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
|
||||
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Passes <a href="https://github.com/crossbario/autobahn-testsuite">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
|
||||
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
|
||||
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
|
||||
|
||||
+2
-2
@@ -70,7 +70,7 @@ type Dialer struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||
// size is zero, then a useful default size is used. The I/O buffer sizes
|
||||
// do not limit the size of the messages that can be sent or received.
|
||||
ReadBufferSize, WriteBufferSize int
|
||||
@@ -140,7 +140,7 @@ var nilDialer = *DefaultDialer
|
||||
// Use the response.Header to get the selected subprotocol
|
||||
// (Sec-WebSocket-Protocol) and cookies (Set-Cookie).
|
||||
//
|
||||
// The context will be used in the request and in the Dialer
|
||||
// The context will be used in the request and in the Dialer.
|
||||
//
|
||||
// If the WebSocket handshake fails, ErrBadHandshake is returned along with a
|
||||
// non-nil *http.Response so that callers can handle redirects, authentication,
|
||||
|
||||
+74
-38
@@ -260,10 +260,12 @@ type Conn struct {
|
||||
newCompressionWriter func(io.WriteCloser, int) io.WriteCloser
|
||||
|
||||
// Read fields
|
||||
reader io.ReadCloser // the current reader returned to the application
|
||||
readErr error
|
||||
br *bufio.Reader
|
||||
readRemaining int64 // bytes remaining in current frame.
|
||||
reader io.ReadCloser // the current reader returned to the application
|
||||
readErr error
|
||||
br *bufio.Reader
|
||||
// bytes remaining in current frame.
|
||||
// set setReadRemaining to safely update this value and prevent overflow
|
||||
readRemaining int64
|
||||
readFinal bool // true the current message has more frames.
|
||||
readLength int64 // Message size.
|
||||
readLimit int64 // Maximum message size.
|
||||
@@ -320,6 +322,17 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int,
|
||||
return c
|
||||
}
|
||||
|
||||
// setReadRemaining tracks the number of bytes remaining on the connection. If n
|
||||
// overflows, an ErrReadLimit is returned.
|
||||
func (c *Conn) setReadRemaining(n int64) error {
|
||||
if n < 0 {
|
||||
return ErrReadLimit
|
||||
}
|
||||
|
||||
c.readRemaining = n
|
||||
return nil
|
||||
}
|
||||
|
||||
// Subprotocol returns the negotiated protocol for the connection.
|
||||
func (c *Conn) Subprotocol() string {
|
||||
return c.subprotocol
|
||||
@@ -451,7 +464,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Conn) prepWrite(messageType int) error {
|
||||
// beginMessage prepares a connection and message writer for a new message.
|
||||
func (c *Conn) beginMessage(mw *messageWriter, messageType int) error {
|
||||
// Close previous writer if not already closed by the application. It's
|
||||
// probably better to return an error in this situation, but we cannot
|
||||
// change this without breaking existing applications.
|
||||
@@ -471,6 +485,10 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
mw.c = c
|
||||
mw.frameType = messageType
|
||||
mw.pos = maxFrameHeaderSize
|
||||
|
||||
if c.writeBuf == nil {
|
||||
wpd, ok := c.writePool.Get().(writePoolData)
|
||||
if ok {
|
||||
@@ -491,16 +509,11 @@ func (c *Conn) prepWrite(messageType int) error {
|
||||
// All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and
|
||||
// PongMessage) are supported.
|
||||
func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) {
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
var mw messageWriter
|
||||
if err := c.beginMessage(&mw, messageType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mw := &messageWriter{
|
||||
c: c,
|
||||
frameType: messageType,
|
||||
pos: maxFrameHeaderSize,
|
||||
}
|
||||
c.writer = mw
|
||||
c.writer = &mw
|
||||
if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) {
|
||||
w := c.newCompressionWriter(c.writer, c.compressionLevel)
|
||||
mw.compress = true
|
||||
@@ -517,10 +530,16 @@ type messageWriter struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (w *messageWriter) fatal(err error) error {
|
||||
func (w *messageWriter) endMessage(err error) error {
|
||||
if w.err != nil {
|
||||
w.err = err
|
||||
w.c.writer = nil
|
||||
return err
|
||||
}
|
||||
c := w.c
|
||||
w.err = err
|
||||
c.writer = nil
|
||||
if c.writePool != nil {
|
||||
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||
c.writeBuf = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -534,7 +553,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
// Check for invalid control frames.
|
||||
if isControl(w.frameType) &&
|
||||
(!final || length > maxControlFramePayloadSize) {
|
||||
return w.fatal(errInvalidControlFrame)
|
||||
return w.endMessage(errInvalidControlFrame)
|
||||
}
|
||||
|
||||
b0 := byte(w.frameType)
|
||||
@@ -579,7 +598,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
copy(c.writeBuf[maxFrameHeaderSize-4:], key[:])
|
||||
maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos])
|
||||
if len(extra) > 0 {
|
||||
return c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))
|
||||
return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode")))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,15 +619,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error {
|
||||
c.isWriting = false
|
||||
|
||||
if err != nil {
|
||||
return w.fatal(err)
|
||||
return w.endMessage(err)
|
||||
}
|
||||
|
||||
if final {
|
||||
c.writer = nil
|
||||
if c.writePool != nil {
|
||||
c.writePool.Put(writePoolData{buf: c.writeBuf})
|
||||
c.writeBuf = nil
|
||||
}
|
||||
w.endMessage(errWriteClosed)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -706,11 +721,7 @@ func (w *messageWriter) Close() error {
|
||||
if w.err != nil {
|
||||
return w.err
|
||||
}
|
||||
if err := w.flushFrame(true, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
w.err = errWriteClosed
|
||||
return nil
|
||||
return w.flushFrame(true, nil)
|
||||
}
|
||||
|
||||
// WritePreparedMessage writes prepared message into connection.
|
||||
@@ -742,10 +753,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error {
|
||||
if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) {
|
||||
// Fast path with no allocations and single frame.
|
||||
|
||||
if err := c.prepWrite(messageType); err != nil {
|
||||
var mw messageWriter
|
||||
if err := c.beginMessage(&mw, messageType); err != nil {
|
||||
return err
|
||||
}
|
||||
mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize}
|
||||
n := copy(c.writeBuf[mw.pos:], data)
|
||||
mw.pos += n
|
||||
data = data[n:]
|
||||
@@ -792,7 +803,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
final := p[0]&finalBit != 0
|
||||
frameType := int(p[0] & 0xf)
|
||||
mask := p[1]&maskBit != 0
|
||||
c.readRemaining = int64(p[1] & 0x7f)
|
||||
c.setReadRemaining(int64(p[1] & 0x7f))
|
||||
|
||||
c.readDecompress = false
|
||||
if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 {
|
||||
@@ -826,7 +837,17 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType))
|
||||
}
|
||||
|
||||
// 3. Read and parse frame length.
|
||||
// 3. Read and parse frame length as per
|
||||
// https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
//
|
||||
// The length of the "Payload data", in bytes: if 0-125, that is the payload
|
||||
// length.
|
||||
// - If 126, the following 2 bytes interpreted as a 16-bit unsigned
|
||||
// integer are the payload length.
|
||||
// - If 127, the following 8 bytes interpreted as
|
||||
// a 64-bit unsigned integer (the most significant bit MUST be 0) are the
|
||||
// payload length. Multibyte length quantities are expressed in network byte
|
||||
// order.
|
||||
|
||||
switch c.readRemaining {
|
||||
case 126:
|
||||
@@ -834,13 +855,19 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
c.readRemaining = int64(binary.BigEndian.Uint16(p))
|
||||
|
||||
if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
case 127:
|
||||
p, err := c.read(8)
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
c.readRemaining = int64(binary.BigEndian.Uint64(p))
|
||||
|
||||
if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Handle frame masking.
|
||||
@@ -863,6 +890,12 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage {
|
||||
|
||||
c.readLength += c.readRemaining
|
||||
// Don't allow readLength to overflow in the presence of a large readRemaining
|
||||
// counter.
|
||||
if c.readLength < 0 {
|
||||
return noFrame, ErrReadLimit
|
||||
}
|
||||
|
||||
if c.readLimit > 0 && c.readLength > c.readLimit {
|
||||
c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait))
|
||||
return noFrame, ErrReadLimit
|
||||
@@ -876,7 +909,7 @@ func (c *Conn) advanceFrame() (int, error) {
|
||||
var payload []byte
|
||||
if c.readRemaining > 0 {
|
||||
payload, err = c.read(int(c.readRemaining))
|
||||
c.readRemaining = 0
|
||||
c.setReadRemaining(0)
|
||||
if err != nil {
|
||||
return noFrame, err
|
||||
}
|
||||
@@ -949,6 +982,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) {
|
||||
c.readErr = hideTempErr(err)
|
||||
break
|
||||
}
|
||||
|
||||
if frameType == TextMessage || frameType == BinaryMessage {
|
||||
c.messageReader = &messageReader{c}
|
||||
c.reader = c.messageReader
|
||||
@@ -989,7 +1023,9 @@ func (r *messageReader) Read(b []byte) (int, error) {
|
||||
if c.isServer {
|
||||
c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n])
|
||||
}
|
||||
c.readRemaining -= int64(n)
|
||||
rem := c.readRemaining
|
||||
rem -= int64(n)
|
||||
c.setReadRemaining(rem)
|
||||
if c.readRemaining > 0 && c.readErr == io.EOF {
|
||||
c.readErr = errUnexpectedEOF
|
||||
}
|
||||
@@ -1041,7 +1077,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error {
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadLimit sets the maximum size for a message read from the peer. If a
|
||||
// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a
|
||||
// message exceeds the limit, the connection sends a close message to the peer
|
||||
// and returns ErrReadLimit to the application.
|
||||
func (c *Conn) SetReadLimit(limit int64) {
|
||||
|
||||
+47
@@ -151,6 +151,53 @@
|
||||
// checking. The application is responsible for checking the Origin header
|
||||
// before calling the Upgrade function.
|
||||
//
|
||||
// Buffers
|
||||
//
|
||||
// Connections buffer network input and output to reduce the number
|
||||
// of system calls when reading or writing messages.
|
||||
//
|
||||
// Write buffers are also used for constructing WebSocket frames. See RFC 6455,
|
||||
// Section 5 for a discussion of message framing. A WebSocket frame header is
|
||||
// written to the network each time a write buffer is flushed to the network.
|
||||
// Decreasing the size of the write buffer can increase the amount of framing
|
||||
// overhead on the connection.
|
||||
//
|
||||
// The buffer sizes in bytes are specified by the ReadBufferSize and
|
||||
// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default
|
||||
// size of 4096 when a buffer size field is set to zero. The Upgrader reuses
|
||||
// buffers created by the HTTP server when a buffer size field is set to zero.
|
||||
// The HTTP server buffers have a size of 4096 at the time of this writing.
|
||||
//
|
||||
// The buffer sizes do not limit the size of a message that can be read or
|
||||
// written by a connection.
|
||||
//
|
||||
// Buffers are held for the lifetime of the connection by default. If the
|
||||
// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the
|
||||
// write buffer only when writing a message.
|
||||
//
|
||||
// Applications should tune the buffer sizes to balance memory use and
|
||||
// performance. Increasing the buffer size uses more memory, but can reduce the
|
||||
// number of system calls to read or write the network. In the case of writing,
|
||||
// increasing the buffer size can reduce the number of frame headers written to
|
||||
// the network.
|
||||
//
|
||||
// Some guidelines for setting buffer parameters are:
|
||||
//
|
||||
// Limit the buffer sizes to the maximum expected message size. Buffers larger
|
||||
// than the largest message do not provide any benefit.
|
||||
//
|
||||
// Depending on the distribution of message sizes, setting the buffer size to
|
||||
// to a value less than the maximum expected message size can greatly reduce
|
||||
// memory use with a small impact on performance. Here's an example: If 99% of
|
||||
// the messages are smaller than 256 bytes and the maximum message size is 512
|
||||
// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls
|
||||
// than a buffer size of 512 bytes. The memory savings is 50%.
|
||||
//
|
||||
// A write buffer pool is useful when the application has a modest number
|
||||
// writes over a large number of connections. when buffers are pooled, a larger
|
||||
// buffer size has a reduced impact on total memory use and has the benefit of
|
||||
// reducing system calls and frame overhead.
|
||||
//
|
||||
// Compression EXPERIMENTAL
|
||||
//
|
||||
// Per message compression extensions (RFC 7692) are experimentally supported
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
module github.com/gorilla/websocket
|
||||
|
||||
go 1.12
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
// Copyright 2019 The Gorilla WebSocket 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 websocket
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// JoinMessages concatenates received messages to create a single io.Reader.
|
||||
// The string term is appended to each message. The returned reader does not
|
||||
// support concurrent calls to the Read method.
|
||||
func JoinMessages(c *Conn, term string) io.Reader {
|
||||
return &joinReader{c: c, term: term}
|
||||
}
|
||||
|
||||
type joinReader struct {
|
||||
c *Conn
|
||||
term string
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (r *joinReader) Read(p []byte) (int, error) {
|
||||
if r.r == nil {
|
||||
var err error
|
||||
_, r.r, err = r.c.NextReader()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if r.term != "" {
|
||||
r.r = io.MultiReader(r.r, strings.NewReader(r.term))
|
||||
}
|
||||
}
|
||||
n, err := r.r.Read(p)
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
r.r = nil
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
+4
-4
@@ -22,18 +22,18 @@ func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) {
|
||||
|
||||
func init() {
|
||||
proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) {
|
||||
return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil
|
||||
return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil
|
||||
})
|
||||
}
|
||||
|
||||
type httpProxyDialer struct {
|
||||
proxyURL *url.URL
|
||||
fowardDial func(network, addr string) (net.Conn, error)
|
||||
proxyURL *url.URL
|
||||
forwardDial func(network, addr string) (net.Conn, error)
|
||||
}
|
||||
|
||||
func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) {
|
||||
hostPort, _ := hostPortNoPort(hpd.proxyURL)
|
||||
conn, err := hpd.fowardDial(network, hostPort)
|
||||
conn, err := hpd.forwardDial(network, hostPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ type Upgrader struct {
|
||||
// HandshakeTimeout specifies the duration for the handshake to complete.
|
||||
HandshakeTimeout time.Duration
|
||||
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer
|
||||
// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
|
||||
// size is zero, then buffers allocated by the HTTP server are used. The
|
||||
// I/O buffer sizes do not limit the size of the messages that can be sent
|
||||
// or received.
|
||||
@@ -153,7 +153,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade
|
||||
|
||||
challengeKey := r.Header.Get("Sec-Websocket-Key")
|
||||
if challengeKey == "" {
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank")
|
||||
return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank")
|
||||
}
|
||||
|
||||
subprotocol := u.selectSubprotocol(r, responseHeader)
|
||||
|
||||
+89
-43
@@ -31,68 +31,113 @@ func generateChallengeKey() (string, error) {
|
||||
return base64.StdEncoding.EncodeToString(p), nil
|
||||
}
|
||||
|
||||
// Octet types from RFC 2616.
|
||||
var octetTypes [256]byte
|
||||
|
||||
const (
|
||||
isTokenOctet = 1 << iota
|
||||
isSpaceOctet
|
||||
)
|
||||
|
||||
func init() {
|
||||
// From RFC 2616
|
||||
//
|
||||
// OCTET = <any 8-bit sequence of data>
|
||||
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
||||
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
||||
// CR = <US-ASCII CR, carriage return (13)>
|
||||
// LF = <US-ASCII LF, linefeed (10)>
|
||||
// SP = <US-ASCII SP, space (32)>
|
||||
// HT = <US-ASCII HT, horizontal-tab (9)>
|
||||
// <"> = <US-ASCII double-quote mark (34)>
|
||||
// CRLF = CR LF
|
||||
// LWS = [CRLF] 1*( SP | HT )
|
||||
// TEXT = <any OCTET except CTLs, but including LWS>
|
||||
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
||||
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
||||
// token = 1*<any CHAR except CTLs or separators>
|
||||
// qdtext = <any TEXT except <">>
|
||||
|
||||
for c := 0; c < 256; c++ {
|
||||
var t byte
|
||||
isCtl := c <= 31 || c == 127
|
||||
isChar := 0 <= c && c <= 127
|
||||
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
||||
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
||||
t |= isSpaceOctet
|
||||
}
|
||||
if isChar && !isCtl && !isSeparator {
|
||||
t |= isTokenOctet
|
||||
}
|
||||
octetTypes[c] = t
|
||||
}
|
||||
// Token octets per RFC 2616.
|
||||
var isTokenOctet = [256]bool{
|
||||
'!': true,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'W': true,
|
||||
'V': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'|': true,
|
||||
'~': true,
|
||||
}
|
||||
|
||||
// skipSpace returns a slice of the string s with all leading RFC 2616 linear
|
||||
// whitespace removed.
|
||||
func skipSpace(s string) (rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isSpaceOctet == 0 {
|
||||
if b := s[i]; b != ' ' && b != '\t' {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[i:]
|
||||
}
|
||||
|
||||
// nextToken returns the leading RFC 2616 token of s and the string following
|
||||
// the token.
|
||||
func nextToken(s string) (token, rest string) {
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if octetTypes[s[i]]&isTokenOctet == 0 {
|
||||
if !isTokenOctet[s[i]] {
|
||||
break
|
||||
}
|
||||
}
|
||||
return s[:i], s[i:]
|
||||
}
|
||||
|
||||
// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616
|
||||
// and the string following the token or quoted string.
|
||||
func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
if !strings.HasPrefix(s, "\"") {
|
||||
return nextToken(s)
|
||||
@@ -128,7 +173,8 @@ func nextTokenOrQuoted(s string) (value string, rest string) {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
|
||||
// equalASCIIFold returns true if s is equal to t with ASCII case folding as
|
||||
// defined in RFC 4790.
|
||||
func equalASCIIFold(s, t string) bool {
|
||||
for s != "" && t != "" {
|
||||
sr, size := utf8.DecodeRuneInString(s)
|
||||
|
||||
+3
-1
@@ -6,4 +6,6 @@
|
||||
*.test
|
||||
|
||||
*.out
|
||||
*.txt
|
||||
*.txt
|
||||
|
||||
vendor/
|
||||
+3
@@ -3,6 +3,9 @@ language: go
|
||||
go:
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Leonardo Di Donato
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module github.com/leodido/go-urn
|
||||
|
||||
go 1.13
|
||||
|
||||
require github.com/stretchr/testify v1.4.0
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
+261
-240
File diff suppressed because it is too large
Load Diff
+26
-4
@@ -1,12 +1,21 @@
|
||||
SHELL := /bin/bash
|
||||
|
||||
build: machine.go
|
||||
|
||||
images: docs/urn.png
|
||||
|
||||
machine.go: machine.go.rl
|
||||
ragel -Z -G2 -e -o $@ $<
|
||||
@gofmt -w -s $@
|
||||
@sed -i '/^\/\/line/d' $@
|
||||
@$(MAKE) -s file=$@ snake2camel
|
||||
@gofmt -w -s $@
|
||||
|
||||
.PHONY: build
|
||||
build: machine.go
|
||||
docs/urn.dot: machine.go.rl
|
||||
@mkdir -p docs
|
||||
ragel -Z -e -Vp $< -o $@
|
||||
|
||||
docs/urn.png: docs/urn.dot
|
||||
dot $< -Tpng -o $@
|
||||
|
||||
.PHONY: bench
|
||||
bench: *_test.go machine.go
|
||||
@@ -14,4 +23,17 @@ bench: *_test.go machine.go
|
||||
|
||||
.PHONY: tests
|
||||
tests: *_test.go machine.go
|
||||
go test -race -timeout 10s -coverprofile=coverage.out -covermode=atomic -v ./...
|
||||
go test -race -timeout 10s -coverprofile=coverage.out -covermode=atomic -v ./...
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
@rm -rf docs
|
||||
@rm -f machine.go
|
||||
|
||||
.PHONY: snake2camel
|
||||
snake2camel:
|
||||
@awk -i inplace '{ \
|
||||
while ( match($$0, /(.*)([a-z]+[0-9]*)_([a-zA-Z0-9])(.*)/, cap) ) \
|
||||
$$0 = cap[1] cap[2] toupper(cap[3]) cap[4]; \
|
||||
print \
|
||||
}' $(file)
|
||||
+8
-7
@@ -1,13 +1,14 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5
|
||||
- ./go.test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
# go-isatty
|
||||
|
||||
[](http://godoc.org/github.com/mattn/go-isatty)
|
||||
[](https://travis-ci.org/mattn/go-isatty)
|
||||
[](https://codecov.io/gh/mattn/go-isatty)
|
||||
[](https://coveralls.io/github/mattn/go-isatty?branch=master)
|
||||
[](https://goreportcard.com/report/mattn/go-isatty)
|
||||
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
module github.com/mattn/go-isatty
|
||||
|
||||
require golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a
|
||||
go 1.12
|
||||
|
||||
require golang.org/x/sys v0.0.0-20200116001909-b77594299b42
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
for d in $(go list ./... | grep -v vendor); do
|
||||
go test -race -coverprofile=profile.out -covermode=atomic "$d"
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
fi
|
||||
done
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
// +build android
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const ioctlReadTermios = syscall.TCGETS
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var termios syscall.Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
+3
-9
@@ -3,18 +3,12 @@
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
var termios syscall.Termios
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
|
||||
return err == 0
|
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TIOCGETA)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
// +build plan9
|
||||
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
func IsTerminal(fd uintptr) bool {
|
||||
path, err := syscall.Fd2path(int(fd))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return path == "/dev/cons" || path == "/mnt/term/dev/cons"
|
||||
}
|
||||
|
||||
// IsCygwinTerminal return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
return false
|
||||
}
|
||||
-1
@@ -1,6 +1,5 @@
|
||||
// +build linux aix
|
||||
// +build !appengine
|
||||
// +build !android
|
||||
|
||||
package isatty
|
||||
|
||||
|
||||
+35
-4
@@ -4,6 +4,7 @@
|
||||
package isatty
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unicode/utf16"
|
||||
@@ -11,15 +12,18 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
fileNameInfo uintptr = 2
|
||||
fileTypePipe = 3
|
||||
objectNameInfo uintptr = 1
|
||||
fileNameInfo = 2
|
||||
fileTypePipe = 3
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
ntdll = syscall.NewLazyDLL("ntdll.dll")
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx")
|
||||
procGetFileType = kernel32.NewProc("GetFileType")
|
||||
procNtQueryObject = ntdll.NewProc("NtQueryObject")
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -45,7 +49,10 @@ func isCygwinPipeName(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if token[0] != `\msys` && token[0] != `\cygwin` {
|
||||
if token[0] != `\msys` &&
|
||||
token[0] != `\cygwin` &&
|
||||
token[0] != `\Device\NamedPipe\msys` &&
|
||||
token[0] != `\Device\NamedPipe\cygwin` {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -68,11 +75,35 @@ func isCygwinPipeName(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// getFileNameByHandle use the undocomented ntdll NtQueryObject to get file full name from file handler
|
||||
// since GetFileInformationByHandleEx is not avilable under windows Vista and still some old fashion
|
||||
// guys are using Windows XP, this is a workaround for those guys, it will also work on system from
|
||||
// Windows vista to 10
|
||||
// see https://stackoverflow.com/a/18792477 for details
|
||||
func getFileNameByHandle(fd uintptr) (string, error) {
|
||||
if procNtQueryObject == nil {
|
||||
return "", errors.New("ntdll.dll: NtQueryObject not supported")
|
||||
}
|
||||
|
||||
var buf [4 + syscall.MAX_PATH]uint16
|
||||
var result int
|
||||
r, _, e := syscall.Syscall6(procNtQueryObject.Addr(), 5,
|
||||
fd, objectNameInfo, uintptr(unsafe.Pointer(&buf)), uintptr(2*len(buf)), uintptr(unsafe.Pointer(&result)), 0)
|
||||
if r != 0 {
|
||||
return "", e
|
||||
}
|
||||
return string(utf16.Decode(buf[4 : 4+buf[0]/2])), nil
|
||||
}
|
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal.
|
||||
func IsCygwinTerminal(fd uintptr) bool {
|
||||
if procGetFileInformationByHandleEx == nil {
|
||||
return false
|
||||
name, err := getFileNameByHandle(fd)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return isCygwinPipeName(name)
|
||||
}
|
||||
|
||||
// Cygwin/msys's pty is a pipe.
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"config:base"
|
||||
],
|
||||
"postUpdateOptions": [
|
||||
"gomodTidy"
|
||||
]
|
||||
}
|
||||
+4
-6
@@ -2,14 +2,12 @@ language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- "1.12.x"
|
||||
- "1.13.x"
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
# don't use the miekg/dns when testing forks
|
||||
- mkdir -p $GOPATH/src/github.com/miekg
|
||||
- ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/miekg/ || true
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
||||
script:
|
||||
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
* @miekg @tmthrgd
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user