feat: init

This commit is contained in:
decentral1se 2023-01-17 11:15:01 +01:00
commit c4a20ccae7
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
43 changed files with 7647 additions and 0 deletions

16
.goreleaser.yml Normal file
View File

@ -0,0 +1,16 @@
---
project_name: kimchi
before:
hooks:
- go mod tidy
builds:
- goos:
- linux
- darwin
goarch:
- amd64
archives:
- format: binary

15
LICENSE Normal file
View File

@ -0,0 +1,15 @@
kimchi: Simple Kimai terminal client 🤟
Copyright (C) 2023 Autonomic Co-op
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# `kimchi`
> Simple Kimai terminal client 🤟
## Usage
1. Grab a binary from [`dist`](./dist) that matches your OS
1. Download and `chmod +x` it
1. `kimchi -h` to see the help output
## Hacking
- `go build -v .` to build
- `go run kimchi.go` to test quickly
- Use `export DEBUG=1` to see every request/response
## Release
1. `goreleaser release --snapshot --rm-dist`
1. `git add . && git commit -m "release: new binaries" && git push`
## Forks
Had to fork [`strfmt`](https://github.com/go-openapi/strfmt) due to [`go-openapi/strfmt#92`](https://github.com/go-openapi/strfmt/issues/92).
Also had to apply this diff:
```diff
diff --git a/strfmt/time.go b/strfmt/time.go
index 748ca40..5dab792 100644
--- a/strfmt/time.go
+++ b/strfmt/time.go
@@ -83,7 +83,8 @@ var (
DateTimeFormats = []string{RFC3339Micro, RFC3339MicroNoColon, RFC3339Millis, RFC3339MillisNoColon, time.RFC3339, time.RFC3339Nano, ISO8601LocalTime, ISO8601PlusTimezone, ISO8601TimeWithReducedPrecision, ISO8601TimeWithReducedPrecisionLocaltime, ISO8601TimeUniversalSortableDateTimePattern}
// MarshalFormat sets the time resolution format used for marshaling time (set to milliseconds)
- MarshalFormat = RFC3339Millis
+ // MarshalFormat = RFC3339Millis
+ MarshalFormat = ISO8601LocalTime
// NormalizeTimeForMarshal provides a normalization function on time befeore marshalling (e.g. time.UTC).
// By default, the time value is not changed.
```
## License
<a><img src="https://www.gnu.org/graphics/gplv3-with-text-136x68.png"/></a>

50
go.mod Normal file
View File

@ -0,0 +1,50 @@
module decentral1se/kimchi
go 1.18
replace github.com/go-openapi/strfmt => ./strfmt
require (
github.com/AlecAivazis/survey/v2 v2.3.6
github.com/decentral1se/go-kimai v0.0.0-20230118194531-adcc0c7317bc
github.com/go-openapi/runtime v0.25.0
github.com/go-openapi/strfmt v0.21.3
github.com/urfave/cli/v2 v2.23.7
)
require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.21.2 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/loads v0.21.1 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-openapi/validate v0.22.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.8 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.mongodb.org/mongo-driver v1.11.1 // indirect
go.opentelemetry.io/otel v1.11.1 // indirect
go.opentelemetry.io/otel/trace v1.11.1 // indirect
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

157
go.sum Normal file
View File

@ -0,0 +1,157 @@
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decentral1se/go-kimai v0.0.0-20230118194531-adcc0c7317bc h1:jZTu+7km2qVE+IgISG1N10NbrWuTrwWYAWLuRIB5rCY=
github.com/decentral1se/go-kimai v0.0.0-20230118194531-adcc0c7317bc/go.mod h1:JLNqCfdxIc/AtnGGksa3CwJoE6ib5FmssdLzl5t1/HI=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.3 h1:rz6kiC84sqNQoqrtulzaL/VERgkoCyB6WdEkc2ujzUc=
github.com/go-openapi/errors v0.20.3/go.mod h1:Z3FlZ4I8jEGxjUK+bugx3on2mIAk4txuAOhlsB1FSgk=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0=
github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc=
github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/validate v0.22.0 h1:b0QecH6VslW/TxtpKgzpO1SNG7GU2FsaqKdP1E2T50Y=
github.com/go-openapi/validate v0.22.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY=
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8=
go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs=
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

401
kimchi.go Normal file
View File

@ -0,0 +1,401 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"log"
"math"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/decentral1se/go-kimai/client"
"github.com/decentral1se/go-kimai/client/activity"
"github.com/decentral1se/go-kimai/client/customer"
"github.com/decentral1se/go-kimai/client/project"
"github.com/decentral1se/go-kimai/client/timesheet"
"github.com/decentral1se/go-kimai/models"
"github.com/AlecAivazis/survey/v2"
"github.com/go-openapi/runtime"
httpTransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
"github.com/urfave/cli/v2"
)
var dateFormat = "2006-01-02T15:04:05"
// homePath is the configuration home directory path.
var homePath = os.ExpandEnv("$HOME/.kimchi")
// confPath is the configuration file path.
var confPath = path.Join(homePath, "conf.txt")
// config stores all user config at run-time.
type config struct {
auth runtime.ClientAuthInfoWriter
client *client.GoKimai
domain string
token string
username string
hourlyRate string
}
// prompt feeds input from user into the configuration.
func (c *config) prompt() error {
usernamePrompt := &survey.Input{Message: "Kimai username?"}
if err := survey.AskOne(usernamePrompt, &c.username); err != nil {
log.Fatalf("Unable to read kimai username: %s", err)
}
tokenPrompt := &survey.Password{Message: "Kimai token?"}
if err := survey.AskOne(tokenPrompt, &c.token); err != nil {
log.Fatalf("Unable to read kimai token: %s", err)
}
hourlyPrompt := &survey.Input{Message: "Hourly rate?"}
if err := survey.AskOne(hourlyPrompt, &c.hourlyRate); err != nil {
log.Fatalf("Unable to read hourly rate: %s", err)
}
if err := c.save(); err != nil {
log.Fatalf("Unable to write config to the FS: %s", err)
}
return nil
}
// toBytes converts a configuration to bytes.
func (c *config) toBytes() []byte {
content := ""
content += fmt.Sprintf("%s\n", c.username)
content += fmt.Sprintf("%s\n", c.token)
content += fmt.Sprintf("%s\n", c.domain)
content += fmt.Sprintf("%s\n", c.hourlyRate)
return []byte(content)
}
// save writes a config to the FS as plain text.
func (c config) save() error {
if err := ioutil.WriteFile(confPath, c.toBytes(), 0644); err != nil {
return fmt.Errorf("Unable to write %s: %s", confPath, err)
}
return nil
}
// load reads a config from the FS.
func (c *config) load() error {
contents, err := ioutil.ReadFile(confPath)
if err != nil {
return fmt.Errorf("Unable to read %s: %s", confPath, err)
}
lines := strings.Split(string(contents), "\n")
if len(lines) < 4 {
return fmt.Errorf("Unable to read %s, it's badly formatted?", confPath)
}
c.username = lines[0]
c.token = lines[1]
c.domain = lines[2]
c.hourlyRate = lines[3]
return nil
}
func (c *config) initClient() {
c.auth = httpTransport.Compose(
httpTransport.APIKeyAuth("X-AUTH-USER", "header", c.username),
httpTransport.APIKeyAuth("X-AUTH-TOKEN", "header", c.token),
)
transport := httpTransport.New(c.domain, "", []string{"https"})
c.client = client.New(transport, strfmt.Default)
}
// selectCustomer allows uers to select a single customer.
func selectCustomer(conf *config) (*models.CustomerCollection, error) {
params := customer.NewGetAPICustomersParams()
params.WithOrder("ASC")
params.WithOrderBy("name")
resp, err := conf.client.Customer.GetAPICustomers(params, conf.auth)
if err != nil {
return &models.CustomerCollection{}, fmt.Errorf("Unable to retrieve customers: %s", err)
}
var names []string
for _, customer := range resp.Payload {
names = append(names, *customer.Name)
}
prompt := &survey.Select{
Message: "Select a customer:",
Options: names,
}
var choice string
if err := survey.AskOne(prompt, &choice, survey.WithPageSize(20)); err != nil {
return &models.CustomerCollection{}, fmt.Errorf("Unable to choose customer name: %s", err)
}
var chosenCustomer *models.CustomerCollection
for _, customer := range resp.Payload {
if choice == *customer.Name {
chosenCustomer = customer
}
}
return chosenCustomer, nil
}
// selectProject allows uers to select a single project.
func selectProject(conf *config, customer *models.CustomerCollection) (*models.ProjectCollection, error) {
params := project.NewGetAPIProjectsParams()
params.Customer = strconv.FormatInt(customer.ID, 10)
params.IgnoreDates = "1"
params.GlobalActivities = "1"
params.WithOrder("ASC")
params.WithOrderBy("name")
resp, err := conf.client.Project.GetAPIProjects(params, conf.auth)
if err != nil {
return &models.ProjectCollection{}, fmt.Errorf("Unable to retrieve projects: %s", err)
}
var names []string
for _, project := range resp.Payload {
names = append(names, *project.Name)
}
prompt := &survey.Select{
Message: "Select a project:",
Options: names,
}
var choice string
if err := survey.AskOne(prompt, &choice, survey.WithPageSize(20)); err != nil {
return &models.ProjectCollection{}, fmt.Errorf("Unable to choose project: %s", err)
}
var chosenProject *models.ProjectCollection
for _, project := range resp.Payload {
if choice == *project.Name {
chosenProject = project
}
}
return chosenProject, nil
}
// selectActivity allows uers to select a single activity.
func selectActivity(conf *config, project *models.ProjectCollection) (*models.ActivityCollection, error) {
params := activity.NewGetAPIActivitiesParams()
params.Project = strconv.FormatInt(project.ID, 10)
params.WithOrder("ASC")
params.WithOrderBy("name")
resp, err := conf.client.Activity.GetAPIActivities(params, conf.auth)
if err != nil {
return &models.ActivityCollection{}, fmt.Errorf("Unable to retrieve activities: %s", err)
}
var names []string
for _, activity := range resp.Payload {
names = append(names, *activity.Name)
}
prompt := &survey.Select{
Message: "Select an activity:",
Options: names,
}
var choice string
if err := survey.AskOne(prompt, &choice, survey.WithPageSize(20)); err != nil {
return &models.ActivityCollection{}, fmt.Errorf("Unable to choose an activity: %s", err)
}
var chosenActivity *models.ActivityCollection
for _, activity := range resp.Payload {
if choice == *activity.Name {
chosenActivity = activity
}
}
return chosenActivity, nil
}
func main() {
app := &cli.App{
Name: "kimchi",
Usage: "Simple Kimai terminal client 🤟",
Description: "Run without sub-commands for current status.",
Before: func(c *cli.Context) error {
conf := config{domain: "kimai.autonomic.zone"}
if err := os.Mkdir(homePath, 0764); err != nil {
if !os.IsExist(err) {
log.Fatalf("Unable to create ~/.kimchi: %s", err)
}
}
if err := conf.load(); err != nil {
if err := conf.prompt(); err != nil {
log.Fatalf("Unable to load config from the FS: %s", err)
}
}
c.Context = context.WithValue(c.Context, "config", conf)
return nil
},
Commands: []*cli.Command{
{
Name: "start",
Aliases: []string{"s"},
Description: "Fuzzy search enabled interface.",
Usage: "Start tracking time",
Action: func(cCtx *cli.Context) error {
conf := cCtx.Context.Value("config").(config)
conf.initClient()
customer, err := selectCustomer(&conf)
if err != nil {
log.Fatal(err)
}
project, err := selectProject(&conf, customer)
if err != nil {
log.Fatal(err)
}
activity, err := selectActivity(&conf, project)
if err != nil {
log.Fatal(err)
}
var description string
descPrompt := &survey.Input{Message: "Description (leave empty to ignore):"}
if err := survey.AskOne(descPrompt, &description); err != nil {
log.Fatalf("Unable to read description: %s", err)
}
var hourly string
hourlyPrompt := &survey.Input{Message: "Hourly:", Default: conf.hourlyRate}
if err := survey.AskOne(hourlyPrompt, &hourly); err != nil {
log.Fatalf("Unable to read hourly: %s", err)
}
parsedHourly, err := strconv.ParseFloat(hourly, 32)
if err != nil {
log.Fatalf("Unable to determine hourly rate: %s", err)
}
parsedHourly = math.Round(parsedHourly*100) / 100 // round to closest
now := strfmt.DateTime(time.Now())
startParams := timesheet.NewPostAPITimesheetsParams()
startParams.WithBody(&models.TimesheetEditForm{
Project: &project.ID,
Activity: &activity.ID,
Begin: &now,
HourlyRate: parsedHourly,
})
if description != "" {
startParams.Body.Description = description
}
_, err = conf.client.Timesheet.PostAPITimesheets(startParams, conf.auth)
if err != nil {
log.Fatalf("Unable to start timesheet entry: %s", err)
}
fmt.Println("Started tracking 👏")
return nil
},
},
{
Name: "stop",
Aliases: []string{"t"},
Usage: "Stop tracking time",
Action: func(cCtx *cli.Context) error {
conf := cCtx.Context.Value("config").(config)
conf.initClient()
timesheetParams := timesheet.NewGetAPITimesheetsActiveParams()
tsResp, err := conf.client.Timesheet.GetAPITimesheetsActive(timesheetParams, conf.auth)
if err != nil {
log.Fatalf("Unable to retrieve active timesheet: %s", err)
}
if len(tsResp.Payload) == 0 {
fmt.Println("No active timesheets currently")
return nil
}
id := tsResp.Payload[0].ID
stopParams := timesheet.NewPatchAPITimesheetsIDStopParams()
stopParams.ID = id
_, err = conf.client.Timesheet.PatchAPITimesheetsIDStop(stopParams, conf.auth)
if err != nil {
log.Fatalf("Unable to stop active timesheet: %s", err)
}
fmt.Println("Tracking stopped 🤚")
return nil
},
},
},
Action: func(c *cli.Context) error {
if c.Args().Len() > 0 { // error out with incorrect sub-commands
if err := cli.ShowSubcommandHelp(c); err != nil {
log.Fatal(err)
}
os.Exit(1)
}
conf := c.Context.Value("config").(config)
conf.initClient()
timesheetParams := timesheet.NewGetAPITimesheetsActiveParams()
resp, err := conf.client.Timesheet.GetAPITimesheetsActive(timesheetParams, conf.auth)
if err != nil {
log.Fatalf("Unable to retrieve active timesheet: %s", err)
}
if len(resp.Payload) == 0 {
fmt.Println("No active timesheets currently")
return nil
}
active := resp.Payload[0]
// FIXME have this show as a nicer duration
begin := time.Time(*active.Begin).Local()
fmt.Printf("👉 %s %s %s\n",
*active.Project.Customer.Name,
*active.Project.Name,
*active.Activity.Name,
)
if active.Description != "" {
fmt.Printf("📓 %s \n", active.Description)
}
fmt.Printf("⏳ %s", begin)
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatalf("Woops, something went wrong: %s", err)
}
}

26
strfmt/.editorconfig Normal file
View File

@ -0,0 +1,26 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
# Set default charset
[*.{js,py,go,scala,rb,java,html,css,less,sass,md}]
charset = utf-8
# Tab indentation (no size specified)
[*.go]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

2
strfmt/.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
*.go text eol=lf

117
strfmt/.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,117 @@
## Contribution Guidelines
### Pull requests are always welcome
We are always thrilled to receive pull requests, and do our best to
process them as fast as possible. Not sure if that typo is worth a pull
request? Do it! We will appreciate it.
If your pull request is not accepted on the first try, don't be
discouraged! If there's a problem with the implementation, hopefully you
received feedback on what to improve.
We're trying very hard to keep go-swagger lean and focused. We don't want it
to do everything for everybody. This means that we might decide against
incorporating a new feature. However, there might be a way to implement
that feature *on top of* go-swagger.
### Conventions
Fork the repo and make changes on your fork in a feature branch:
- If it's a bugfix branch, name it XXX-something where XXX is the number of the
issue
- If it's a feature branch, create an enhancement issue to announce your
intentions, and name it XXX-something where XXX is the number of the issue.
Submit unit tests for your changes. Go has a great test framework built in; use
it! Take a look at existing tests for inspiration. Run the full test suite on
your branch before submitting a pull request.
Update the documentation when creating or modifying features. Test
your documentation changes for clarity, concision, and correctness, as
well as a clean documentation build. See ``docs/README.md`` for more
information on building the docs and how docs get released.
Write clean code. Universally formatted code promotes ease of writing, reading,
and maintenance. Always run `gofmt -s -w file.go` on each changed file before
committing your changes. Most editors have plugins that do this automatically.
Pull requests descriptions should be as clear as possible and include a
reference to all the issues that they address.
Pull requests must not contain commits from other users or branches.
Commit messages must start with a capitalized and short summary (max. 50
chars) written in the imperative, followed by an optional, more detailed
explanatory text which is separated from the summary by an empty line.
Code review comments may be added to your pull request. Discuss, then make the
suggested modifications and push additional commits to your feature branch. Be
sure to post a comment after pushing. The new commits will show up in the pull
request automatically, but the reviewers will not be notified unless you
comment.
Before the pull request is merged, make sure that you squash your commits into
logical units of work using `git rebase -i` and `git push -f`. After every
commit the test suite should be passing. Include documentation changes in the
same commit so that a revert would remove all traces of the feature or fix.
Commits that fix or close an issue should include a reference like `Closes #XXX`
or `Fixes #XXX`, which will automatically close the issue when merged.
### Sign your work
The sign-off is a simple line at the end of the explanation for the
patch, which certifies that you wrote it or otherwise have the right to
pass it on as an open-source patch. The rules are pretty simple: if you
can certify the below (from
[developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe@gmail.com>
using your real name (sorry, no pseudonyms or anonymous contributions.)
You can add the sign off when creating the git commit via `git commit -s`.

46
strfmt/.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,46 @@
name: Go
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
fail-fast: false
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.17.1
- name: Setup gotestsum
uses: autero1/action-gotestsum@v1.0.0
with:
gotestsum_version: 1.7.0
- name: Test
run: gotestsum --format short-verbose -- -race -timeout=20m -coverprofile=coverage_txt -covermode=atomic ./...
- uses: codecov/codecov-action@v2
with:
files: coverage_txt
lint:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
fail-fast: false
steps:
- uses: actions/checkout@v2
- uses: golangci/golangci-lint-action@v2
with:
args: --timeout=5m

2
strfmt/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
secrets.yml
coverage.out

50
strfmt/.golangci.yml Normal file
View File

@ -0,0 +1,50 @@
linters-settings:
govet:
check-shadowing: true
golint:
min-confidence: 0
gocyclo:
min-complexity: 31
maligned:
suggest-new: true
dupl:
threshold: 100
goconst:
min-len: 2
min-occurrences: 4
linters:
enable-all: true
disable:
- maligned
- lll
- gochecknoinits
- gochecknoglobals
- godox
- gocognit
- whitespace
- wsl
- funlen
- wrapcheck
- testpackage
- nlreturn
- gofumpt
- goerr113
- gci
- gomnd
- godot
- exhaustivestruct
- paralleltest
- varnamelen
- ireturn
- exhaustruct
#- thelper
issues:
exclude-rules:
- path: bson.go
text: "should be .*ObjectID"
linters:
- golint
- stylecheck

74
strfmt/CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,74 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at ivan+abuse@flanders.co.nz. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/

202
strfmt/LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

88
strfmt/README.md Normal file
View File

@ -0,0 +1,88 @@
# Strfmt [![Build Status](https://travis-ci.org/go-openapi/strfmt.svg?branch=master)](https://travis-ci.org/go-openapi/strfmt) [![codecov](https://codecov.io/gh/go-openapi/strfmt/branch/master/graph/badge.svg)](https://codecov.io/gh/go-openapi/strfmt) [![Slack Status](https://slackin.goswagger.io/badge.svg)](https://slackin.goswagger.io)
[![license](http://img.shields.io/badge/license-Apache%20v2-orange.svg)](https://raw.githubusercontent.com/go-openapi/strfmt/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/go-openapi/strfmt?status.svg)](http://godoc.org/github.com/go-openapi/strfmt)
[![GolangCI](https://golangci.com/badges/github.com/go-openapi/strfmt.svg)](https://golangci.com)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-openapi/strfmt)](https://goreportcard.com/report/github.com/go-openapi/strfmt)
This package exposes a registry of data types to support string formats in the go-openapi toolkit.
strfmt represents a well known string format such as credit card or email. The go toolkit for OpenAPI specifications knows how to deal with those.
## Supported data formats
go-openapi/strfmt follows the swagger 2.0 specification with the following formats
defined [here](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types).
It also provides convenient extensions to go-openapi users.
- [x] JSON-schema draft 4 formats
- date-time
- email
- hostname
- ipv4
- ipv6
- uri
- [x] swagger 2.0 format extensions
- binary
- byte (e.g. base64 encoded string)
- date (e.g. "1970-01-01")
- password
- [x] go-openapi custom format extensions
- bsonobjectid (BSON objectID)
- creditcard
- duration (e.g. "3 weeks", "1ms")
- hexcolor (e.g. "#FFFFFF")
- isbn, isbn10, isbn13
- mac (e.g "01:02:03:04:05:06")
- rgbcolor (e.g. "rgb(100,100,100)")
- ssn
- uuid, uuid3, uuid4, uuid5
- cidr (e.g. "192.0.2.1/24", "2001:db8:a0b:12f0::1/32")
- ulid (e.g. "00000PP9HGSBSSDZ1JTEXBJ0PW", [spec](https://github.com/ulid/spec))
> NOTE: as the name stands for, this package is intended to support string formatting only.
> It does not provide validation for numerical values with swagger format extension for JSON types "number" or
> "integer" (e.g. float, double, int32...).
## Type conversion
All types defined here are stringers and may be converted to strings with `.String()`.
Note that most types defined by this package may be converted directly to string like `string(Email{})`.
`Date` and `DateTime` may be converted directly to `time.Time` like `time.Time(Time{})`.
Similarly, you can convert `Duration` to `time.Duration` as in `time.Duration(Duration{})`
## Using pointers
The `conv` subpackage provides helpers to convert the types to and from pointers, just like `go-openapi/swag` does
with primitive types.
## Format types
Types defined in strfmt expose marshaling and validation capabilities.
List of defined types:
- Base64
- CreditCard
- Date
- DateTime
- Duration
- Email
- HexColor
- Hostname
- IPv4
- IPv6
- CIDR
- ISBN
- ISBN10
- ISBN13
- MAC
- ObjectId
- Password
- RGBColor
- SSN
- URI
- UUID
- UUID3
- UUID4
- UUID5
- [ULID](https://github.com/ulid/spec)

165
strfmt/bson.go Normal file
View File

@ -0,0 +1,165 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"database/sql/driver"
"fmt"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
bsonprim "go.mongodb.org/mongo-driver/bson/primitive"
)
func init() {
var id ObjectId
// register this format in the default registry
Default.Add("bsonobjectid", &id, IsBSONObjectID)
}
// IsBSONObjectID returns true when the string is a valid BSON.ObjectId
func IsBSONObjectID(str string) bool {
_, err := bsonprim.ObjectIDFromHex(str)
return err == nil
}
// ObjectId represents a BSON object ID (alias to go.mongodb.org/mongo-driver/bson/primitive.ObjectID)
//
// swagger:strfmt bsonobjectid
type ObjectId bsonprim.ObjectID //nolint:revive
// NewObjectId creates a ObjectId from a Hex String
func NewObjectId(hex string) ObjectId { //nolint:revive
oid, err := bsonprim.ObjectIDFromHex(hex)
if err != nil {
panic(err)
}
return ObjectId(oid)
}
// MarshalText turns this instance into text
func (id ObjectId) MarshalText() ([]byte, error) {
oid := bsonprim.ObjectID(id)
if oid == bsonprim.NilObjectID {
return nil, nil
}
return []byte(oid.Hex()), nil
}
// UnmarshalText hydrates this instance from text
func (id *ObjectId) UnmarshalText(data []byte) error { // validation is performed later on
if len(data) == 0 {
*id = ObjectId(bsonprim.NilObjectID)
return nil
}
oidstr := string(data)
oid, err := bsonprim.ObjectIDFromHex(oidstr)
if err != nil {
return err
}
*id = ObjectId(oid)
return nil
}
// Scan read a value from a database driver
func (id *ObjectId) Scan(raw interface{}) error {
var data []byte
switch v := raw.(type) {
case []byte:
data = v
case string:
data = []byte(v)
default:
return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v", v)
}
return id.UnmarshalText(data)
}
// Value converts a value to a database driver value
func (id ObjectId) Value() (driver.Value, error) {
return driver.Value(bsonprim.ObjectID(id).Hex()), nil
}
func (id ObjectId) String() string {
return bsonprim.ObjectID(id).Hex()
}
// MarshalJSON returns the ObjectId as JSON
func (id ObjectId) MarshalJSON() ([]byte, error) {
return bsonprim.ObjectID(id).MarshalJSON()
}
// UnmarshalJSON sets the ObjectId from JSON
func (id *ObjectId) UnmarshalJSON(data []byte) error {
var obj bsonprim.ObjectID
if err := obj.UnmarshalJSON(data); err != nil {
return err
}
*id = ObjectId(obj)
return nil
}
// MarshalBSON renders the object id as a BSON document
func (id ObjectId) MarshalBSON() ([]byte, error) {
return bson.Marshal(bson.M{"data": bsonprim.ObjectID(id)})
}
// UnmarshalBSON reads the objectId from a BSON document
func (id *ObjectId) UnmarshalBSON(data []byte) error {
var obj struct {
Data bsonprim.ObjectID
}
if err := bson.Unmarshal(data, &obj); err != nil {
return err
}
*id = ObjectId(obj.Data)
return nil
}
// MarshalBSONValue is an interface implemented by types that can marshal themselves
// into a BSON document represented as bytes. The bytes returned must be a valid
// BSON document if the error is nil.
func (id ObjectId) MarshalBSONValue() (bsontype.Type, []byte, error) {
oid := bsonprim.ObjectID(id)
return bsontype.ObjectID, oid[:], nil
}
// UnmarshalBSONValue is an interface implemented by types that can unmarshal a
// BSON value representation of themselves. The BSON bytes and type can be
// assumed to be valid. UnmarshalBSONValue must copy the BSON value bytes if it
// wishes to retain the data after returning.
func (id *ObjectId) UnmarshalBSONValue(tpe bsontype.Type, data []byte) error {
var oid bsonprim.ObjectID
copy(oid[:], data)
*id = ObjectId(oid)
return nil
}
// DeepCopyInto copies the receiver and writes its value into out.
func (id *ObjectId) DeepCopyInto(out *ObjectId) {
*out = *id
}
// DeepCopy copies the receiver into a new ObjectId.
func (id *ObjectId) DeepCopy() *ObjectId {
if id == nil {
return nil
}
out := new(ObjectId)
id.DeepCopyInto(out)
return out
}

68
strfmt/bson_test.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
)
func TestBSONObjectId_fullCycle(t *testing.T) {
id := NewObjectId("507f1f77bcf86cd799439011")
bytes, err := id.MarshalText()
assert.NoError(t, err)
var idCopy ObjectId
err = idCopy.Scan(bytes)
assert.NoError(t, err)
assert.Equal(t, id, idCopy)
err = idCopy.UnmarshalText(bytes)
assert.NoError(t, err)
assert.Equal(t, id, idCopy)
jsonBytes, err := id.MarshalJSON()
assert.NoError(t, err)
err = idCopy.UnmarshalJSON(jsonBytes)
assert.NoError(t, err)
assert.Equal(t, id, idCopy)
bsonBytes, err := bson.Marshal(&id)
assert.NoError(t, err)
err = bson.Unmarshal(bsonBytes, &idCopy)
assert.NoError(t, err)
assert.Equal(t, id, idCopy)
}
func TestDeepCopyObjectId(t *testing.T) {
id := NewObjectId("507f1f77bcf86cd799439011")
in := &id
out := new(ObjectId)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *ObjectId
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}

18
strfmt/conv/date.go Normal file
View File

@ -0,0 +1,18 @@
package conv
import "github.com/go-openapi/strfmt"
// Date returns a pointer to of the Date value passed in.
func Date(v strfmt.Date) *strfmt.Date {
return &v
}
// DateValue returns the value of the Date pointer passed in or
// the default value if the pointer is nil.
func DateValue(v *strfmt.Date) strfmt.Date {
if v == nil {
return strfmt.Date{}
}
return *v
}

16
strfmt/conv/date_test.go Normal file
View File

@ -0,0 +1,16 @@
package conv
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/go-openapi/strfmt"
)
func TestDateValue(t *testing.T) {
assert.Equal(t, strfmt.Date{}, DateValue(nil))
date := strfmt.Date(time.Now())
assert.Equal(t, date, DateValue(&date))
}

305
strfmt/conv/default.go Normal file
View File

@ -0,0 +1,305 @@
package conv
import (
"github.com/go-openapi/strfmt"
)
// Base64 returns a pointer to of the Base64 value passed in.
func Base64(v strfmt.Base64) *strfmt.Base64 {
return &v
}
// Base64Value returns the value of the Base64 pointer passed in or
// the default value if the pointer is nil.
func Base64Value(v *strfmt.Base64) strfmt.Base64 {
if v == nil {
return nil
}
return *v
}
// URI returns a pointer to of the URI value passed in.
func URI(v strfmt.URI) *strfmt.URI {
return &v
}
// URIValue returns the value of the URI pointer passed in or
// the default value if the pointer is nil.
func URIValue(v *strfmt.URI) strfmt.URI {
if v == nil {
return strfmt.URI("")
}
return *v
}
// Email returns a pointer to of the Email value passed in.
func Email(v strfmt.Email) *strfmt.Email {
return &v
}
// EmailValue returns the value of the Email pointer passed in or
// the default value if the pointer is nil.
func EmailValue(v *strfmt.Email) strfmt.Email {
if v == nil {
return strfmt.Email("")
}
return *v
}
// Hostname returns a pointer to of the Hostname value passed in.
func Hostname(v strfmt.Hostname) *strfmt.Hostname {
return &v
}
// HostnameValue returns the value of the Hostname pointer passed in or
// the default value if the pointer is nil.
func HostnameValue(v *strfmt.Hostname) strfmt.Hostname {
if v == nil {
return strfmt.Hostname("")
}
return *v
}
// IPv4 returns a pointer to of the IPv4 value passed in.
func IPv4(v strfmt.IPv4) *strfmt.IPv4 {
return &v
}
// IPv4Value returns the value of the IPv4 pointer passed in or
// the default value if the pointer is nil.
func IPv4Value(v *strfmt.IPv4) strfmt.IPv4 {
if v == nil {
return strfmt.IPv4("")
}
return *v
}
// IPv6 returns a pointer to of the IPv6 value passed in.
func IPv6(v strfmt.IPv6) *strfmt.IPv6 {
return &v
}
// IPv6Value returns the value of the IPv6 pointer passed in or
// the default value if the pointer is nil.
func IPv6Value(v *strfmt.IPv6) strfmt.IPv6 {
if v == nil {
return strfmt.IPv6("")
}
return *v
}
// CIDR returns a pointer to of the CIDR value passed in.
func CIDR(v strfmt.CIDR) *strfmt.CIDR {
return &v
}
// CIDRValue returns the value of the CIDR pointer passed in or
// the default value if the pointer is nil.
func CIDRValue(v *strfmt.CIDR) strfmt.CIDR {
if v == nil {
return strfmt.CIDR("")
}
return *v
}
// MAC returns a pointer to of the MAC value passed in.
func MAC(v strfmt.MAC) *strfmt.MAC {
return &v
}
// MACValue returns the value of the MAC pointer passed in or
// the default value if the pointer is nil.
func MACValue(v *strfmt.MAC) strfmt.MAC {
if v == nil {
return strfmt.MAC("")
}
return *v
}
// UUID returns a pointer to of the UUID value passed in.
func UUID(v strfmt.UUID) *strfmt.UUID {
return &v
}
// UUIDValue returns the value of the UUID pointer passed in or
// the default value if the pointer is nil.
func UUIDValue(v *strfmt.UUID) strfmt.UUID {
if v == nil {
return strfmt.UUID("")
}
return *v
}
// UUID3 returns a pointer to of the UUID3 value passed in.
func UUID3(v strfmt.UUID3) *strfmt.UUID3 {
return &v
}
// UUID3Value returns the value of the UUID3 pointer passed in or
// the default value if the pointer is nil.
func UUID3Value(v *strfmt.UUID3) strfmt.UUID3 {
if v == nil {
return strfmt.UUID3("")
}
return *v
}
// UUID4 returns a pointer to of the UUID4 value passed in.
func UUID4(v strfmt.UUID4) *strfmt.UUID4 {
return &v
}
// UUID4Value returns the value of the UUID4 pointer passed in or
// the default value if the pointer is nil.
func UUID4Value(v *strfmt.UUID4) strfmt.UUID4 {
if v == nil {
return strfmt.UUID4("")
}
return *v
}
// UUID5 returns a pointer to of the UUID5 value passed in.
func UUID5(v strfmt.UUID5) *strfmt.UUID5 {
return &v
}
// UUID5Value returns the value of the UUID5 pointer passed in or
// the default value if the pointer is nil.
func UUID5Value(v *strfmt.UUID5) strfmt.UUID5 {
if v == nil {
return strfmt.UUID5("")
}
return *v
}
// ISBN returns a pointer to of the ISBN value passed in.
func ISBN(v strfmt.ISBN) *strfmt.ISBN {
return &v
}
// ISBNValue returns the value of the ISBN pointer passed in or
// the default value if the pointer is nil.
func ISBNValue(v *strfmt.ISBN) strfmt.ISBN {
if v == nil {
return strfmt.ISBN("")
}
return *v
}
// ISBN10 returns a pointer to of the ISBN10 value passed in.
func ISBN10(v strfmt.ISBN10) *strfmt.ISBN10 {
return &v
}
// ISBN10Value returns the value of the ISBN10 pointer passed in or
// the default value if the pointer is nil.
func ISBN10Value(v *strfmt.ISBN10) strfmt.ISBN10 {
if v == nil {
return strfmt.ISBN10("")
}
return *v
}
// ISBN13 returns a pointer to of the ISBN13 value passed in.
func ISBN13(v strfmt.ISBN13) *strfmt.ISBN13 {
return &v
}
// ISBN13Value returns the value of the ISBN13 pointer passed in or
// the default value if the pointer is nil.
func ISBN13Value(v *strfmt.ISBN13) strfmt.ISBN13 {
if v == nil {
return strfmt.ISBN13("")
}
return *v
}
// CreditCard returns a pointer to of the CreditCard value passed in.
func CreditCard(v strfmt.CreditCard) *strfmt.CreditCard {
return &v
}
// CreditCardValue returns the value of the CreditCard pointer passed in or
// the default value if the pointer is nil.
func CreditCardValue(v *strfmt.CreditCard) strfmt.CreditCard {
if v == nil {
return strfmt.CreditCard("")
}
return *v
}
// SSN returns a pointer to of the SSN value passed in.
func SSN(v strfmt.SSN) *strfmt.SSN {
return &v
}
// SSNValue returns the value of the SSN pointer passed in or
// the default value if the pointer is nil.
func SSNValue(v *strfmt.SSN) strfmt.SSN {
if v == nil {
return strfmt.SSN("")
}
return *v
}
// HexColor returns a pointer to of the HexColor value passed in.
func HexColor(v strfmt.HexColor) *strfmt.HexColor {
return &v
}
// HexColorValue returns the value of the HexColor pointer passed in or
// the default value if the pointer is nil.
func HexColorValue(v *strfmt.HexColor) strfmt.HexColor {
if v == nil {
return strfmt.HexColor("")
}
return *v
}
// RGBColor returns a pointer to of the RGBColor value passed in.
func RGBColor(v strfmt.RGBColor) *strfmt.RGBColor {
return &v
}
// RGBColorValue returns the value of the RGBColor pointer passed in or
// the default value if the pointer is nil.
func RGBColorValue(v *strfmt.RGBColor) strfmt.RGBColor {
if v == nil {
return strfmt.RGBColor("")
}
return *v
}
// Password returns a pointer to of the Password value passed in.
func Password(v strfmt.Password) *strfmt.Password {
return &v
}
// PasswordValue returns the value of the Password pointer passed in or
// the default value if the pointer is nil.
func PasswordValue(v *strfmt.Password) strfmt.Password {
if v == nil {
return strfmt.Password("")
}
return *v
}

129
strfmt/conv/default_test.go Normal file
View File

@ -0,0 +1,129 @@
package conv
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/go-openapi/strfmt"
)
func TestBase64Value(t *testing.T) {
assert.Equal(t, strfmt.Base64(nil), Base64Value(nil))
base64 := strfmt.Base64([]byte{4, 2})
assert.Equal(t, base64, Base64Value(&base64))
}
func TestURIValue(t *testing.T) {
assert.Equal(t, strfmt.URI(""), URIValue(nil))
value := strfmt.URI("foo")
assert.Equal(t, value, URIValue(&value))
}
func TestEmailValue(t *testing.T) {
assert.Equal(t, strfmt.Email(""), EmailValue(nil))
value := strfmt.Email("foo")
assert.Equal(t, value, EmailValue(&value))
}
func TestHostnameValue(t *testing.T) {
assert.Equal(t, strfmt.Hostname(""), HostnameValue(nil))
value := strfmt.Hostname("foo")
assert.Equal(t, value, HostnameValue(&value))
}
func TestIPv4Value(t *testing.T) {
assert.Equal(t, strfmt.IPv4(""), IPv4Value(nil))
value := strfmt.IPv4("foo")
assert.Equal(t, value, IPv4Value(&value))
}
func TestIPv6Value(t *testing.T) {
assert.Equal(t, strfmt.IPv6(""), IPv6Value(nil))
value := strfmt.IPv6("foo")
assert.Equal(t, value, IPv6Value(&value))
}
func TestCIDRValue(t *testing.T) {
assert.Equal(t, strfmt.CIDR(""), CIDRValue(nil))
value := strfmt.CIDR("foo")
assert.Equal(t, value, CIDRValue(&value))
}
func TestMACValue(t *testing.T) {
assert.Equal(t, strfmt.MAC(""), MACValue(nil))
value := strfmt.MAC("foo")
assert.Equal(t, value, MACValue(&value))
}
func TestUUIDValue(t *testing.T) {
assert.Equal(t, strfmt.UUID(""), UUIDValue(nil))
value := strfmt.UUID("foo")
assert.Equal(t, value, UUIDValue(&value))
}
func TestUUID3Value(t *testing.T) {
assert.Equal(t, strfmt.UUID3(""), UUID3Value(nil))
value := strfmt.UUID3("foo")
assert.Equal(t, value, UUID3Value(&value))
}
func TestUUID4Value(t *testing.T) {
assert.Equal(t, strfmt.UUID4(""), UUID4Value(nil))
value := strfmt.UUID4("foo")
assert.Equal(t, value, UUID4Value(&value))
}
func TestUUID5Value(t *testing.T) {
assert.Equal(t, strfmt.UUID5(""), UUID5Value(nil))
value := strfmt.UUID5("foo")
assert.Equal(t, value, UUID5Value(&value))
}
func TestISBNValue(t *testing.T) {
assert.Equal(t, strfmt.ISBN(""), ISBNValue(nil))
value := strfmt.ISBN("foo")
assert.Equal(t, value, ISBNValue(&value))
}
func TestISBN10Value(t *testing.T) {
assert.Equal(t, strfmt.ISBN10(""), ISBN10Value(nil))
value := strfmt.ISBN10("foo")
assert.Equal(t, value, ISBN10Value(&value))
}
func TestISBN13Value(t *testing.T) {
assert.Equal(t, strfmt.ISBN13(""), ISBN13Value(nil))
value := strfmt.ISBN13("foo")
assert.Equal(t, value, ISBN13Value(&value))
}
func TestCreditCardValue(t *testing.T) {
assert.Equal(t, strfmt.CreditCard(""), CreditCardValue(nil))
value := strfmt.CreditCard("foo")
assert.Equal(t, value, CreditCardValue(&value))
}
func TestSSNValue(t *testing.T) {
assert.Equal(t, strfmt.SSN(""), SSNValue(nil))
value := strfmt.SSN("foo")
assert.Equal(t, value, SSNValue(&value))
}
func TestHexColorValue(t *testing.T) {
assert.Equal(t, strfmt.HexColor(""), HexColorValue(nil))
value := strfmt.HexColor("foo")
assert.Equal(t, value, HexColorValue(&value))
}
func TestRGBColorValue(t *testing.T) {
assert.Equal(t, strfmt.RGBColor(""), RGBColorValue(nil))
value := strfmt.RGBColor("foo")
assert.Equal(t, value, RGBColorValue(&value))
}
func TestPasswordValue(t *testing.T) {
assert.Equal(t, strfmt.Password(""), PasswordValue(nil))
value := strfmt.Password("foo")
assert.Equal(t, value, PasswordValue(&value))
}

18
strfmt/conv/duration.go Normal file
View File

@ -0,0 +1,18 @@
package conv
import "github.com/go-openapi/strfmt"
// Duration returns a pointer to of the Duration value passed in.
func Duration(v strfmt.Duration) *strfmt.Duration {
return &v
}
// DurationValue returns the value of the Duration pointer passed in or
// the default value if the pointer is nil.
func DurationValue(v *strfmt.Duration) strfmt.Duration {
if v == nil {
return strfmt.Duration(0)
}
return *v
}

View File

@ -0,0 +1,15 @@
package conv
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/go-openapi/strfmt"
)
func TestDurationValue(t *testing.T) {
assert.Equal(t, strfmt.Duration(0), DurationValue(nil))
duration := strfmt.Duration(42)
assert.Equal(t, duration, DurationValue(&duration))
}

18
strfmt/conv/time.go Normal file
View File

@ -0,0 +1,18 @@
package conv
import "github.com/go-openapi/strfmt"
// DateTime returns a pointer to of the DateTime value passed in.
func DateTime(v strfmt.DateTime) *strfmt.DateTime {
return &v
}
// DateTimeValue returns the value of the DateTime pointer passed in or
// the default value if the pointer is nil.
func DateTimeValue(v *strfmt.DateTime) strfmt.DateTime {
if v == nil {
return strfmt.DateTime{}
}
return *v
}

16
strfmt/conv/time_test.go Normal file
View File

@ -0,0 +1,16 @@
package conv
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/go-openapi/strfmt"
)
func TestDateTimeValue(t *testing.T) {
assert.Equal(t, strfmt.DateTime{}, DateTimeValue(nil))
time := strfmt.DateTime(time.Now())
assert.Equal(t, time, DateTimeValue(&time))
}

18
strfmt/conv/ulid.go Normal file
View File

@ -0,0 +1,18 @@
package conv
import "github.com/go-openapi/strfmt"
// ULID returns a pointer to of the ULID value passed in.
func ULID(v strfmt.ULID) *strfmt.ULID {
return &v
}
// ULIDValue returns the value of the ULID pointer passed in or
// the default value if the pointer is nil.
func ULIDValue(v *strfmt.ULID) strfmt.ULID {
if v == nil {
return strfmt.ULID{}
}
return *v
}

22
strfmt/conv/ulid_test.go Normal file
View File

@ -0,0 +1,22 @@
package conv
import (
"testing"
"github.com/go-openapi/strfmt"
"github.com/stretchr/testify/assert"
)
const testUlid = string("01EYXZVGBHG26MFTG4JWR4K558")
func TestULIDValue(t *testing.T) {
assert.Equal(t, strfmt.ULID{}, ULIDValue(nil))
value := strfmt.ULID{}
err := value.UnmarshalText([]byte(testUlid))
assert.NoError(t, err)
assert.Equal(t, value, ULIDValue(&value))
ulidRef := ULID(value)
assert.Equal(t, &value, ulidRef)
}

187
strfmt/date.go Normal file
View File

@ -0,0 +1,187 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"time"
"go.mongodb.org/mongo-driver/bson"
)
func init() {
d := Date{}
// register this format in the default registry
Default.Add("date", &d, IsDate)
}
// IsDate returns true when the string is a valid date
func IsDate(str string) bool {
_, err := time.Parse(RFC3339FullDate, str)
return err == nil
}
const (
// RFC3339FullDate represents a full-date as specified by RFC3339
// See: http://goo.gl/xXOvVd
RFC3339FullDate = "2006-01-02"
)
// Date represents a date from the API
//
// swagger:strfmt date
type Date time.Time
// String converts this date into a string
func (d Date) String() string {
return time.Time(d).Format(RFC3339FullDate)
}
// UnmarshalText parses a text representation into a date type
func (d *Date) UnmarshalText(text []byte) error {
if len(text) == 0 {
return nil
}
dd, err := time.Parse(RFC3339FullDate, string(text))
if err != nil {
return err
}
*d = Date(dd)
return nil
}
// MarshalText serializes this date type to string
func (d Date) MarshalText() ([]byte, error) {
return []byte(d.String()), nil
}
// Scan scans a Date value from database driver type.
func (d *Date) Scan(raw interface{}) error {
switch v := raw.(type) {
case []byte:
return d.UnmarshalText(v)
case string:
return d.UnmarshalText([]byte(v))
case time.Time:
*d = Date(v)
return nil
case nil:
*d = Date{}
return nil
default:
return fmt.Errorf("cannot sql.Scan() strfmt.Date from: %#v", v)
}
}
// Value converts Date to a primitive value ready to written to a database.
func (d Date) Value() (driver.Value, error) {
return driver.Value(d.String()), nil
}
// MarshalJSON returns the Date as JSON
func (d Date) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(d).Format(RFC3339FullDate))
}
// UnmarshalJSON sets the Date from JSON
func (d *Date) UnmarshalJSON(data []byte) error {
if string(data) == jsonNull {
return nil
}
var strdate string
if err := json.Unmarshal(data, &strdate); err != nil {
return err
}
tt, err := time.Parse(RFC3339FullDate, strdate)
if err != nil {
return err
}
*d = Date(tt)
return nil
}
func (d Date) MarshalBSON() ([]byte, error) {
return bson.Marshal(bson.M{"data": d.String()})
}
func (d *Date) UnmarshalBSON(data []byte) error {
var m bson.M
if err := bson.Unmarshal(data, &m); err != nil {
return err
}
if data, ok := m["data"].(string); ok {
rd, err := time.Parse(RFC3339FullDate, data)
if err != nil {
return err
}
*d = Date(rd)
return nil
}
return errors.New("couldn't unmarshal bson bytes value as Date")
}
// DeepCopyInto copies the receiver and writes its value into out.
func (d *Date) DeepCopyInto(out *Date) {
*out = *d
}
// DeepCopy copies the receiver into a new Date.
func (d *Date) DeepCopy() *Date {
if d == nil {
return nil
}
out := new(Date)
d.DeepCopyInto(out)
return out
}
// GobEncode implements the gob.GobEncoder interface.
func (d Date) GobEncode() ([]byte, error) {
return d.MarshalBinary()
}
// GobDecode implements the gob.GobDecoder interface.
func (d *Date) GobDecode(data []byte) error {
return d.UnmarshalBinary(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (d Date) MarshalBinary() ([]byte, error) {
return time.Time(d).MarshalBinary()
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (d *Date) UnmarshalBinary(data []byte) error {
var original time.Time
err := original.UnmarshalBinary(data)
if err != nil {
return err
}
*d = Date(original)
return nil
}
// Equal checks if two Date instances are equal
func (d Date) Equal(d2 Date) bool {
return time.Time(d).Equal(time.Time(d2))
}

175
strfmt/date_test.go Normal file
View File

@ -0,0 +1,175 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"bytes"
"database/sql"
"database/sql/driver"
"encoding/gob"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
)
var _ sql.Scanner = &Date{}
var _ driver.Valuer = Date{}
func TestDate(t *testing.T) {
pp := Date{}
err := pp.UnmarshalText([]byte{})
assert.NoError(t, err)
err = pp.UnmarshalText([]byte("yada"))
assert.Error(t, err)
orig := "2014-12-15"
bj := []byte("\"" + orig + "\"")
err = pp.UnmarshalText([]byte(orig))
assert.NoError(t, err)
txt, err := pp.MarshalText()
assert.NoError(t, err)
assert.Equal(t, orig, string(txt))
err = pp.UnmarshalJSON(bj)
assert.NoError(t, err)
assert.EqualValues(t, orig, pp.String())
err = pp.UnmarshalJSON([]byte(`"1972/01/01"`))
assert.Error(t, err)
b, err := pp.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, bj, b)
dateOriginal := Date(time.Date(2014, 10, 10, 0, 0, 0, 0, time.UTC))
bsonData, err := bson.Marshal(&dateOriginal)
assert.NoError(t, err)
var dateCopy Date
err = bson.Unmarshal(bsonData, &dateCopy)
assert.NoError(t, err)
assert.Equal(t, dateOriginal, dateCopy)
var dateZero Date
err = dateZero.UnmarshalJSON([]byte(jsonNull))
assert.NoError(t, err)
assert.Equal(t, Date{}, dateZero)
}
func TestDate_Scan(t *testing.T) {
ref := time.Now().Truncate(24 * time.Hour).UTC()
date, str := Date(ref), ref.Format(RFC3339FullDate)
values := []interface{}{str, []byte(str), ref}
for _, value := range values {
result := Date{}
_ = (&result).Scan(value)
assert.Equal(t, date, result, "value: %#v", value)
}
dd := Date{}
err := dd.Scan(nil)
assert.NoError(t, err)
assert.Equal(t, Date{}, dd)
err = dd.Scan(19700101)
assert.Error(t, err)
}
func TestDate_Value(t *testing.T) {
ref := time.Now().Truncate(24 * time.Hour).UTC()
date := Date(ref)
dbv, err := date.Value()
assert.NoError(t, err)
assert.EqualValues(t, dbv, ref.Format("2006-01-02"))
}
func TestDate_IsDate(t *testing.T) {
tests := []struct {
value string
valid bool
}{
{"2017-12-22", true},
{"2017-1-1", false},
{"17-13-22", false},
{"2017-02-29", false}, // not a valid date : 2017 is not a leap year
{"1900-02-29", false}, // not a valid date : 1900 is not a leap year
{"2100-02-29", false}, // not a valid date : 2100 is not a leap year
{"2000-02-29", true}, // a valid date : 2000 is a leap year
{"2400-02-29", true}, // a valid date : 2000 is a leap year
{"2017-13-22", false},
{"2017-12-32", false},
{"20171-12-32", false},
{"YYYY-MM-DD", false},
{"20-17-2017", false},
{"2017-12-22T01:02:03Z", false},
}
for _, test := range tests {
assert.Equal(t, test.valid, IsDate(test.value), "value [%s] should be valid: [%t]", test.value, test.valid)
}
}
func TestDeepCopyDate(t *testing.T) {
ref := time.Now().Truncate(24 * time.Hour).UTC()
date := Date(ref)
in := &date
out := new(Date)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *Date
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestGobEncodingDate(t *testing.T) {
now := time.Now()
b := bytes.Buffer{}
enc := gob.NewEncoder(&b)
err := enc.Encode(Date(now))
assert.NoError(t, err)
assert.NotEmpty(t, b.Bytes())
var result Date
dec := gob.NewDecoder(&b)
err = dec.Decode(&result)
assert.NoError(t, err)
assert.Equal(t, now.Year(), time.Time(result).Year())
assert.Equal(t, now.Month(), time.Time(result).Month())
assert.Equal(t, now.Day(), time.Time(result).Day())
}
func TestDate_Equal(t *testing.T) {
t.Parallel()
d1 := Date(time.Date(2020, 10, 11, 12, 13, 14, 15, time.UTC))
d2 := Date(time.Date(2020, 10, 11, 12, 13, 14, 15, time.UTC))
d3 := Date(time.Date(2020, 11, 12, 13, 14, 15, 16, time.UTC))
//nolint:gocritic
assert.True(t, d1.Equal(d1), "Same Date should Equal itself")
assert.True(t, d1.Equal(d2), "Date instances should be equal")
assert.False(t, d1.Equal(d3), "Date instances should not be equal")
}

2035
strfmt/default.go Normal file

File diff suppressed because it is too large Load Diff

776
strfmt/default_test.go Normal file
View File

@ -0,0 +1,776 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"database/sql"
"database/sql/driver"
"encoding"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
)
func TestFormatURI(t *testing.T) {
uri := URI("http://somewhere.com")
str := "http://somewhereelse.com"
testStringFormat(t, &uri, "uri", str, []string{}, []string{"somewhere.com"})
}
func TestFormatEmail(t *testing.T) {
email := Email("somebody@somewhere.com")
str := string("somebodyelse@somewhere.com")
validEmails := []string{
"blah@gmail.com",
"test@d.verylongtoplevel",
"email+tag@gmail.com",
`" "@example.com`,
`"Abc\@def"@example.com`,
`"Fred Bloggs"@example.com`,
`"Joe\\Blow"@example.com`,
`"Abc@def"@example.com`,
"customer/department=shipping@example.com",
"$A12345@example.com",
"!def!xyz%abc@example.com",
"_somename@example.com",
"!#$%&'*+-/=?^_`{}|~@example.com",
"Miles.O'Brian@example.com",
"postmaster@☁→❄→☃→☀→☺→☂→☹→✝.ws",
"root@localhost",
"john@com",
"api@piston.ninja",
}
testStringFormat(t, &email, "email", str, validEmails, []string{"somebody@somewhere@com"})
}
func TestFormatHostname(t *testing.T) {
hostname := Hostname("somewhere.com")
str := string("somewhere.com")
veryLongStr := strings.Repeat("a", 256)
longStr := strings.Repeat("a", 64)
longAddrSegment := strings.Join([]string{"x", "y", longStr}, ".")
invalidHostnames := []string{
"somewhere.com!",
"user@email.domain",
"1.1.1.1",
veryLongStr,
longAddrSegment,
// dashes
"www.example-.org",
"www.--example.org",
"-www.example.org",
"www-.example.org",
"www.d-.org",
"www.-d.org",
"www-",
"-www",
// other characters (not in symbols)
"www.ex ample.org",
"_www.example.org",
"www.ex;ample.org",
"www.example_underscored.org",
// short top-level domains
"www.詹姆斯.x",
"a.b.c.d",
"-xyz",
"xyz-",
"x.",
"a.b.c.dot-",
"a.b.c.é;ö",
}
validHostnames := []string{
"somewhere.com",
"888.com",
"a.com",
"a.b.com",
"a.b.c.com",
"a.b.c.d.com",
"a.b.c.d.e.com",
"1.com",
"1.2.com",
"1.2.3.com",
"1.2.3.4.com",
"99.domain.com",
"99.99.domain.com",
"1wwworg.example.com", // valid, per RFC1123
"1000wwworg.example.com",
"xn--bcher-kva.example.com", // puny encoded
"xn-80ak6aa92e.co",
"xn-80ak6aa92e.com",
"xn--ls8h.la",
"☁→❄→☃→☀→☺→☂→☹→✝.ws",
"www.example.onion",
"www.example.ôlà",
"ôlà.ôlà",
"ôlà.ôlà.ôlà",
"ex$ample",
"localhost",
"example",
"x",
"x-y",
"a.b.c.dot",
"www.example.org",
"a.b.c.d.e.f.g.dot",
// extended symbol alphabet
"ex=ample.com",
"<foo>",
"www.example-hyphenated.org",
// localized hostnames
"www.詹姆斯.org",
"www.élégigôö.org",
// long top-level domains
"www.詹姆斯.london",
}
testStringFormat(t, &hostname, "hostname", str, []string{}, invalidHostnames)
testStringFormat(t, &hostname, "hostname", str, validHostnames, []string{})
}
func TestFormatIPv4(t *testing.T) {
ipv4 := IPv4("192.168.254.1")
str := string("192.168.254.2")
testStringFormat(t, &ipv4, "ipv4", str, []string{}, []string{"198.168.254.2.2"})
}
func TestFormatIPv6(t *testing.T) {
ipv6 := IPv6("::1")
str := string("::2")
// TODO: test ipv6 zones
testStringFormat(t, &ipv6, "ipv6", str, []string{}, []string{"127.0.0.1"})
}
func TestFormatCIDR(t *testing.T) {
cidr := CIDR("192.168.254.1/24")
str := string("192.168.254.2/24")
testStringFormat(t, &cidr, "cidr", str, []string{"192.0.2.1/24", "2001:db8:a0b:12f0::1/32"}, []string{"198.168.254.2", "2001:db8:a0b:12f0::1"})
}
func TestFormatMAC(t *testing.T) {
mac := MAC("01:02:03:04:05:06")
str := string("06:05:04:03:02:01")
testStringFormat(t, &mac, "mac", str, []string{}, []string{"01:02:03:04:05"})
}
func TestFormatUUID3(t *testing.T) {
first3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com"))
other3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhereelse.com"))
uuid3 := UUID3(first3.String())
str := other3.String()
testStringFormat(t, &uuid3, "uuid3", str, []string{}, []string{"not-a-uuid"})
// special case for zero UUID
var uuidZero UUID3
err := uuidZero.UnmarshalJSON([]byte(jsonNull))
assert.NoError(t, err)
assert.EqualValues(t, UUID3(""), uuidZero)
}
func TestFormatUUID4(t *testing.T) {
first4 := uuid.Must(uuid.NewRandom())
other4 := uuid.Must(uuid.NewRandom())
uuid4 := UUID4(first4.String())
str := other4.String()
testStringFormat(t, &uuid4, "uuid4", str, []string{}, []string{"not-a-uuid"})
// special case for zero UUID
var uuidZero UUID4
err := uuidZero.UnmarshalJSON([]byte(jsonNull))
assert.NoError(t, err)
assert.EqualValues(t, UUID4(""), uuidZero)
}
func TestFormatUUID5(t *testing.T) {
first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com"))
other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))
uuid5 := UUID5(first5.String())
str := other5.String()
testStringFormat(t, &uuid5, "uuid5", str, []string{}, []string{"not-a-uuid"})
// special case for zero UUID
var uuidZero UUID5
err := uuidZero.UnmarshalJSON([]byte(jsonNull))
assert.NoError(t, err)
assert.EqualValues(t, UUID5(""), uuidZero)
}
func TestFormatUUID(t *testing.T) {
first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com"))
other5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhereelse.com"))
uuid := UUID(first5.String())
str := other5.String()
testStringFormat(t, &uuid, "uuid", str, []string{}, []string{"not-a-uuid"})
// special case for zero UUID
var uuidZero UUID
err := uuidZero.UnmarshalJSON([]byte(jsonNull))
assert.NoError(t, err)
assert.EqualValues(t, UUID(""), uuidZero)
}
func TestFormatISBN(t *testing.T) {
isbn := ISBN("0321751043")
str := string("0321751043")
testStringFormat(t, &isbn, "isbn", str, []string{}, []string{"836217463"}) // bad checksum
}
func TestFormatISBN10(t *testing.T) {
isbn10 := ISBN10("0321751043")
str := string("0321751043")
testStringFormat(t, &isbn10, "isbn10", str, []string{}, []string{"836217463"}) // bad checksum
}
func TestFormatISBN13(t *testing.T) {
isbn13 := ISBN13("978-0321751041")
str := string("978-0321751041")
testStringFormat(t, &isbn13, "isbn13", str, []string{}, []string{"978-0321751042"}) // bad checksum
}
func TestFormatHexColor(t *testing.T) {
hexColor := HexColor("#FFFFFF")
str := string("#000000")
testStringFormat(t, &hexColor, "hexcolor", str, []string{}, []string{"#fffffffz"})
}
func TestFormatRGBColor(t *testing.T) {
rgbColor := RGBColor("rgb(255,255,255)")
str := string("rgb(0,0,0)")
testStringFormat(t, &rgbColor, "rgbcolor", str, []string{}, []string{"rgb(300,0,0)"})
}
func TestFormatSSN(t *testing.T) {
ssn := SSN("111-11-1111")
str := string("999 99 9999")
testStringFormat(t, &ssn, "ssn", str, []string{}, []string{"999 99 999"})
}
func TestFormatCreditCard(t *testing.T) {
creditCard := CreditCard("4111-1111-1111-1111")
str := string("4012-8888-8888-1881")
testStringFormat(t, &creditCard, "creditcard", str, []string{}, []string{"9999-9999-9999-999"})
}
func TestFormatPassword(t *testing.T) {
password := Password("super secret stuff here")
testStringFormat(t, &password, "password", "super secret!!!", []string{"even more secret"}, []string{})
}
func TestFormatBase64(t *testing.T) {
const b64 string = "This is a byte array with unprintable chars, but it also isn"
str := base64.URLEncoding.EncodeToString([]byte(b64))
b := []byte(b64)
expected := Base64(b)
bj := []byte("\"" + str + "\"")
var subj Base64
err := subj.UnmarshalText([]byte(str))
assert.NoError(t, err)
assert.EqualValues(t, expected, subj)
b, err = subj.MarshalText()
assert.NoError(t, err)
assert.Equal(t, []byte(str), b)
var subj2 Base64
err = subj2.UnmarshalJSON(bj)
assert.NoError(t, err)
assert.EqualValues(t, expected, subj2)
b, err = subj2.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, bj, b)
bsonData, err := bson.Marshal(subj2)
assert.NoError(t, err)
var b64Copy Base64
err = bson.Unmarshal(bsonData, &b64Copy)
assert.NoError(t, err)
assert.Equal(t, subj2, b64Copy)
testValid(t, "byte", str)
testInvalid(t, "byte", "ZWxpemFiZXRocG9zZXk") // missing pad char
// Valuer interface
sqlvalue, err := subj2.Value()
assert.NoError(t, err)
sqlvalueAsString, ok := sqlvalue.(string)
if assert.Truef(t, ok, "[%s]Value: expected driver value to be a string", "byte") {
assert.EqualValuesf(t, str, sqlvalueAsString, "[%s]Value: expected %v and %v to be equal", "byte", sqlvalue, str)
}
// Scanner interface
var subj3 Base64
err = subj3.Scan([]byte(str))
assert.NoError(t, err)
assert.EqualValues(t, str, subj3.String())
var subj4 Base64
err = subj4.Scan(str)
assert.NoError(t, err)
assert.EqualValues(t, str, subj4.String())
err = subj4.Scan(123)
assert.Error(t, err)
}
type testableFormat interface {
encoding.TextMarshaler
encoding.TextUnmarshaler
json.Marshaler
json.Unmarshaler
bson.Marshaler
bson.Unmarshaler
fmt.Stringer
sql.Scanner
driver.Valuer
}
func testStringFormat(t *testing.T, what testableFormat, format, with string, validSamples, invalidSamples []string) {
t.Helper()
// text encoding interface
b := []byte(with)
err := what.UnmarshalText(b)
assert.NoError(t, err)
val := reflect.Indirect(reflect.ValueOf(what))
strVal := val.String()
assert.Equalf(t, with, strVal, "[%s]UnmarshalText: expected %v and %v to be value equal", format, strVal, with)
b, err = what.MarshalText()
assert.NoError(t, err)
assert.Equalf(t, []byte(with), b, "[%s]MarshalText: expected %v and %v to be value equal as []byte", format, string(b), with)
// Stringer
strVal = what.String()
assert.Equalf(t, []byte(with), b, "[%s]String: expected %v and %v to be equal", strVal, with)
// JSON encoding interface
bj := []byte("\"" + with + "\"")
err = what.UnmarshalJSON(bj)
assert.NoError(t, err)
val = reflect.Indirect(reflect.ValueOf(what))
strVal = val.String()
assert.EqualValuesf(t, with, strVal, "[%s]UnmarshalJSON: expected %v and %v to be value equal", format, strVal, with)
b, err = what.MarshalJSON()
assert.NoError(t, err)
assert.Equalf(t, bj, b, "[%s]MarshalJSON: expected %v and %v to be value equal as []byte", format, string(b), with)
// bson encoding interface
bsonData, err := bson.Marshal(what)
assert.NoError(t, err)
resetValue(t, format, what)
err = bson.Unmarshal(bsonData, what)
assert.NoError(t, err)
val = reflect.Indirect(reflect.ValueOf(what))
strVal = val.String()
assert.EqualValuesf(t, with, strVal, "[%s]bson.Unmarshal: expected %v and %v to be equal (reset value) ", format, what, with)
// Scanner interface
resetValue(t, format, what)
err = what.Scan(with)
assert.NoError(t, err)
val = reflect.Indirect(reflect.ValueOf(what))
strVal = val.String()
assert.EqualValuesf(t, with, strVal, "[%s]Scan: expected %v and %v to be value equal", format, strVal, with)
err = what.Scan([]byte(with))
assert.NoError(t, err)
val = reflect.Indirect(reflect.ValueOf(what))
strVal = val.String()
assert.EqualValuesf(t, with, strVal, "[%s]Scan: expected %v and %v to be value equal", format, strVal, with)
err = what.Scan(123)
assert.Error(t, err)
// Valuer interface
sqlvalue, err := what.Value()
assert.NoError(t, err)
sqlvalueAsString, ok := sqlvalue.(string)
if assert.Truef(t, ok, "[%s]Value: expected driver value to be a string", format) {
assert.EqualValuesf(t, with, sqlvalueAsString, "[%s]Value: expected %v and %v to be equal", format, sqlvalue, with)
}
// validation with Registry
for _, valid := range append(validSamples, with) {
testValid(t, format, valid)
}
for _, invalid := range invalidSamples {
testInvalid(t, format, invalid)
}
}
func resetValue(t *testing.T, format string, what encoding.TextUnmarshaler) {
t.Helper()
err := what.UnmarshalText([]byte("reset value"))
assert.NoError(t, err)
val := reflect.Indirect(reflect.ValueOf(what))
strVal := val.String()
assert.Equalf(t, "reset value", strVal, "[%s]UnmarshalText: expected %v and %v to be equal (reset value) ", format, strVal, "reset value")
}
func testValid(t *testing.T, name, value string) {
t.Helper()
ok := Default.Validates(name, value)
if !ok {
t.Errorf("expected %q of type %s to be valid", value, name)
}
}
func testInvalid(t *testing.T, name, value string) {
t.Helper()
ok := Default.Validates(name, value)
if ok {
t.Errorf("expected %q of type %s to be invalid", value, name)
}
}
func TestDeepCopyBase64(t *testing.T) {
b64 := Base64("ZWxpemFiZXRocG9zZXk=")
in := &b64
out := new(Base64)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *Base64
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyURI(t *testing.T) {
uri := URI("http://somewhere.com")
in := &uri
out := new(URI)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *URI
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyEmail(t *testing.T) {
email := Email("somebody@somewhere.com")
in := &email
out := new(Email)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *Email
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyHostname(t *testing.T) {
hostname := Hostname("somewhere.com")
in := &hostname
out := new(Hostname)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *Hostname
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyIPv4(t *testing.T) {
ipv4 := IPv4("192.168.254.1")
in := &ipv4
out := new(IPv4)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *IPv4
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyIPv6(t *testing.T) {
ipv6 := IPv6("::1")
in := &ipv6
out := new(IPv6)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *IPv6
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyCIDR(t *testing.T) {
cidr := CIDR("192.0.2.1/24")
in := &cidr
out := new(CIDR)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *CIDR
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyMAC(t *testing.T) {
mac := MAC("01:02:03:04:05:06")
in := &mac
out := new(MAC)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *MAC
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyUUID(t *testing.T) {
first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com"))
uuid := UUID(first5.String())
in := &uuid
out := new(UUID)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *UUID
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyUUID3(t *testing.T) {
first3 := uuid.NewMD5(uuid.NameSpaceURL, []byte("somewhere.com"))
uuid3 := UUID3(first3.String())
in := &uuid3
out := new(UUID3)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *UUID3
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyUUID4(t *testing.T) {
first4 := uuid.Must(uuid.NewRandom())
uuid4 := UUID4(first4.String())
in := &uuid4
out := new(UUID4)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *UUID4
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyUUID5(t *testing.T) {
first5 := uuid.NewSHA1(uuid.NameSpaceURL, []byte("somewhere.com"))
uuid5 := UUID5(first5.String())
in := &uuid5
out := new(UUID5)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *UUID5
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyISBN(t *testing.T) {
isbn := ISBN("0321751043")
in := &isbn
out := new(ISBN)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *ISBN
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyISBN10(t *testing.T) {
isbn10 := ISBN10("0321751043")
in := &isbn10
out := new(ISBN10)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *ISBN10
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyISBN13(t *testing.T) {
isbn13 := ISBN13("978-0321751041")
in := &isbn13
out := new(ISBN13)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *ISBN13
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyCreditCard(t *testing.T) {
creditCard := CreditCard("4111-1111-1111-1111")
in := &creditCard
out := new(CreditCard)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *CreditCard
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopySSN(t *testing.T) {
ssn := SSN("111-11-1111")
in := &ssn
out := new(SSN)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *SSN
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyHexColor(t *testing.T) {
hexColor := HexColor("#FFFFFF")
in := &hexColor
out := new(HexColor)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *HexColor
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyRGBColor(t *testing.T) {
rgbColor := RGBColor("rgb(255,255,255)")
in := &rgbColor
out := new(RGBColor)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *RGBColor
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestDeepCopyPassword(t *testing.T) {
password := Password("super secret stuff here")
in := &password
out := new(Password)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *Password
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}

18
strfmt/doc.go Normal file
View File

@ -0,0 +1,18 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package strfmt contains custom string formats
//
// TODO: add info on how to define and register a custom format
package strfmt

211
strfmt/duration.go Normal file
View File

@ -0,0 +1,211 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
)
func init() {
d := Duration(0)
// register this format in the default registry
Default.Add("duration", &d, IsDuration)
}
var (
timeUnits = [][]string{
{"ns", "nano"},
{"us", "µs", "micro"},
{"ms", "milli"},
{"s", "sec"},
{"m", "min"},
{"h", "hr", "hour"},
{"d", "day"},
{"w", "wk", "week"},
}
timeMultiplier = map[string]time.Duration{
"ns": time.Nanosecond,
"us": time.Microsecond,
"ms": time.Millisecond,
"s": time.Second,
"m": time.Minute,
"h": time.Hour,
"d": 24 * time.Hour,
"w": 7 * 24 * time.Hour,
}
durationMatcher = regexp.MustCompile(`((\d+)\s*([A-Za-zµ]+))`)
)
// IsDuration returns true if the provided string is a valid duration
func IsDuration(str string) bool {
_, err := ParseDuration(str)
return err == nil
}
// Duration represents a duration
//
// Duration stores a period of time as a nanosecond count, with the largest
// repesentable duration being approximately 290 years.
//
// swagger:strfmt duration
type Duration time.Duration
// MarshalText turns this instance into text
func (d Duration) MarshalText() ([]byte, error) {
return []byte(time.Duration(d).String()), nil
}
// UnmarshalText hydrates this instance from text
func (d *Duration) UnmarshalText(data []byte) error { // validation is performed later on
dd, err := ParseDuration(string(data))
if err != nil {
return err
}
*d = Duration(dd)
return nil
}
// ParseDuration parses a duration from a string, compatible with scala duration syntax
func ParseDuration(cand string) (time.Duration, error) {
if dur, err := time.ParseDuration(cand); err == nil {
return dur, nil
}
var dur time.Duration
ok := false
for _, match := range durationMatcher.FindAllStringSubmatch(cand, -1) {
factor, err := strconv.Atoi(match[2]) // converts string to int
if err != nil {
return 0, err
}
unit := strings.ToLower(strings.TrimSpace(match[3]))
for _, variants := range timeUnits {
last := len(variants) - 1
multiplier := timeMultiplier[variants[0]]
for i, variant := range variants {
if (last == i && strings.HasPrefix(unit, variant)) || strings.EqualFold(variant, unit) {
ok = true
dur += (time.Duration(factor) * multiplier)
}
}
}
}
if ok {
return dur, nil
}
return 0, fmt.Errorf("unable to parse %s as duration", cand)
}
// Scan reads a Duration value from database driver type.
func (d *Duration) Scan(raw interface{}) error {
switch v := raw.(type) {
// TODO: case []byte: // ?
case int64:
*d = Duration(v)
case float64:
*d = Duration(int64(v))
case nil:
*d = Duration(0)
default:
return fmt.Errorf("cannot sql.Scan() strfmt.Duration from: %#v", v)
}
return nil
}
// Value converts Duration to a primitive value ready to be written to a database.
func (d Duration) Value() (driver.Value, error) {
return driver.Value(int64(d)), nil
}
// String converts this duration to a string
func (d Duration) String() string {
return time.Duration(d).String()
}
// MarshalJSON returns the Duration as JSON
func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
}
// UnmarshalJSON sets the Duration from JSON
func (d *Duration) UnmarshalJSON(data []byte) error {
if string(data) == jsonNull {
return nil
}
var dstr string
if err := json.Unmarshal(data, &dstr); err != nil {
return err
}
tt, err := ParseDuration(dstr)
if err != nil {
return err
}
*d = Duration(tt)
return nil
}
func (d Duration) MarshalBSON() ([]byte, error) {
return bson.Marshal(bson.M{"data": d.String()})
}
func (d *Duration) UnmarshalBSON(data []byte) error {
var m bson.M
if err := bson.Unmarshal(data, &m); err != nil {
return err
}
if data, ok := m["data"].(string); ok {
rd, err := ParseDuration(data)
if err != nil {
return err
}
*d = Duration(rd)
return nil
}
return errors.New("couldn't unmarshal bson bytes value as Date")
}
// DeepCopyInto copies the receiver and writes its value into out.
func (d *Duration) DeepCopyInto(out *Duration) {
*out = *d
}
// DeepCopy copies the receiver into a new Duration.
func (d *Duration) DeepCopy() *Duration {
if d == nil {
return nil
}
out := new(Duration)
d.DeepCopyInto(out)
return out
}

225
strfmt/duration_test.go Normal file
View File

@ -0,0 +1,225 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
)
func TestDuration(t *testing.T) {
pp := Duration(0)
err := pp.UnmarshalText([]byte("0ms"))
assert.NoError(t, err)
err = pp.UnmarshalText([]byte("yada"))
assert.Error(t, err)
orig := "2ms"
b := []byte(orig)
bj := []byte("\"" + orig + "\"")
err = pp.UnmarshalText(b)
assert.NoError(t, err)
err = pp.UnmarshalText([]byte("three week"))
assert.Error(t, err)
err = pp.UnmarshalText([]byte("9999999999999999999999999999999999999999999999999999999 weeks"))
assert.Error(t, err)
txt, err := pp.MarshalText()
assert.NoError(t, err)
assert.Equal(t, orig, string(txt))
err = pp.UnmarshalJSON(bj)
assert.NoError(t, err)
assert.EqualValues(t, orig, pp.String())
err = pp.UnmarshalJSON([]byte("yada"))
assert.Error(t, err)
err = pp.UnmarshalJSON([]byte(`"12 parsecs"`))
assert.Error(t, err)
err = pp.UnmarshalJSON([]byte(`"12 y"`))
assert.Error(t, err)
b, err = pp.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, bj, b)
dur := Duration(42)
bsonData, err := bson.Marshal(&dur)
assert.NoError(t, err)
var durCopy Duration
err = bson.Unmarshal(bsonData, &durCopy)
assert.NoError(t, err)
assert.Equal(t, dur, durCopy)
}
func testDurationParser(t *testing.T, toParse string, expected time.Duration) {
t.Helper()
r, e := ParseDuration(toParse)
assert.NoError(t, e)
assert.Equal(t, expected, r)
}
func TestDurationParser_Failed(t *testing.T) {
_, e := ParseDuration("45 wekk")
assert.Error(t, e)
}
func TestIsDuration_Failed(t *testing.T) {
e := IsDuration("45 weeekks")
assert.False(t, e)
}
func testDurationSQLScanner(t *testing.T, dur time.Duration) {
t.Helper()
values := []interface{}{int64(dur), float64(dur)}
for _, value := range values {
var result Duration
err := result.Scan(value)
assert.NoError(t, err)
assert.Equal(t, dur, time.Duration(result))
// And the other way around
resv, erv := result.Value()
assert.NoError(t, erv)
assert.EqualValues(t, value, resv)
}
}
func TestDurationScanner_Nil(t *testing.T) {
var result Duration
err := result.Scan(nil)
assert.NoError(t, err)
assert.EqualValues(t, 0, time.Duration(result))
err = result.Scan("1 ms")
assert.Error(t, err)
}
func TestDurationParser(t *testing.T) {
testcases := map[string]time.Duration{
// parse the short forms without spaces
"1ns": 1 * time.Nanosecond,
"1us": 1 * time.Microsecond,
"1µs": 1 * time.Microsecond,
"1ms": 1 * time.Millisecond,
"1s": 1 * time.Second,
"1m": 1 * time.Minute,
"1h": 1 * time.Hour,
"1hr": 1 * time.Hour,
"1d": 24 * time.Hour,
"1w": 7 * 24 * time.Hour,
"1wk": 7 * 24 * time.Hour,
// parse the long forms without spaces
"1nanoseconds": 1 * time.Nanosecond,
"1nanos": 1 * time.Nanosecond,
"1microseconds": 1 * time.Microsecond,
"1micros": 1 * time.Microsecond,
"1millis": 1 * time.Millisecond,
"1milliseconds": 1 * time.Millisecond,
"1second": 1 * time.Second,
"1sec": 1 * time.Second,
"1min": 1 * time.Minute,
"1minute": 1 * time.Minute,
"1hour": 1 * time.Hour,
"1day": 24 * time.Hour,
"1week": 7 * 24 * time.Hour,
// parse the short forms with spaces
"1 ns": 1 * time.Nanosecond,
"1 us": 1 * time.Microsecond,
"1 µs": 1 * time.Microsecond,
"1 ms": 1 * time.Millisecond,
"1 s": 1 * time.Second,
"1 m": 1 * time.Minute,
"1 h": 1 * time.Hour,
"1 hr": 1 * time.Hour,
"1 d": 24 * time.Hour,
"1 w": 7 * 24 * time.Hour,
"1 wk": 7 * 24 * time.Hour,
// parse the long forms without spaces
"1 nanoseconds": 1 * time.Nanosecond,
"1 nanos": 1 * time.Nanosecond,
"1 microseconds": 1 * time.Microsecond,
"1 micros": 1 * time.Microsecond,
"1 millis": 1 * time.Millisecond,
"1 milliseconds": 1 * time.Millisecond,
"1 second": 1 * time.Second,
"1 sec": 1 * time.Second,
"1 min": 1 * time.Minute,
"1 minute": 1 * time.Minute,
"1 hour": 1 * time.Hour,
"1 day": 24 * time.Hour,
"1 week": 7 * 24 * time.Hour,
}
for str, dur := range testcases {
testDurationParser(t, str, dur)
testDurationSQLScanner(t, dur)
}
}
func TestIsDuration_Caveats(t *testing.T) {
// This works too
e := IsDuration("45 weeks")
assert.True(t, e)
// This works too
e = IsDuration("45 weekz")
assert.True(t, e)
// This works too
e = IsDuration("12 hours")
assert.True(t, e)
// This works too
e = IsDuration("12 minutes")
assert.True(t, e)
// This does not work
e = IsDuration("12 phours")
assert.False(t, e)
}
func TestDeepCopyDuration(t *testing.T) {
dur := Duration(42)
in := &dur
out := new(Duration)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *Duration
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}

326
strfmt/format.go Normal file
View File

@ -0,0 +1,326 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"encoding"
"fmt"
"reflect"
"strings"
"sync"
"time"
"github.com/go-openapi/errors"
"github.com/mitchellh/mapstructure"
)
// Default is the default formats registry
var Default = NewSeededFormats(nil, nil)
// Validator represents a validator for a string format.
type Validator func(string) bool
// Format represents a string format.
//
// All implementations of Format provide a string representation and text
// marshaling/unmarshaling interface to be used by encoders (e.g. encoding/json).
type Format interface {
String() string
encoding.TextMarshaler
encoding.TextUnmarshaler
}
// Registry is a registry of string formats, with a validation method.
type Registry interface {
Add(string, Format, Validator) bool
DelByName(string) bool
GetType(string) (reflect.Type, bool)
ContainsName(string) bool
Validates(string, string) bool
Parse(string, string) (interface{}, error)
MapStructureHookFunc() mapstructure.DecodeHookFunc
}
type knownFormat struct {
Name string
OrigName string
Type reflect.Type
Validator Validator
}
// NameNormalizer is a function that normalizes a format name.
type NameNormalizer func(string) string
// DefaultNameNormalizer removes all dashes
func DefaultNameNormalizer(name string) string {
return strings.ReplaceAll(name, "-", "")
}
type defaultFormats struct {
sync.Mutex
data []knownFormat
normalizeName NameNormalizer
}
// NewFormats creates a new formats registry seeded with the values from the default
func NewFormats() Registry {
//nolint:forcetypeassert
return NewSeededFormats(Default.(*defaultFormats).data, nil)
}
// NewSeededFormats creates a new formats registry
func NewSeededFormats(seeds []knownFormat, normalizer NameNormalizer) Registry {
if normalizer == nil {
normalizer = DefaultNameNormalizer
}
// copy here, don't modify original
d := append([]knownFormat(nil), seeds...)
return &defaultFormats{
data: d,
normalizeName: normalizer,
}
}
// MapStructureHookFunc is a decode hook function for mapstructure
func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc { //nolint:gocyclo,cyclop
return func(from reflect.Type, to reflect.Type, obj interface{}) (interface{}, error) {
if from.Kind() != reflect.String {
return obj, nil
}
data, ok := obj.(string)
if !ok {
return nil, fmt.Errorf("failed to cast %+v to string", obj)
}
for _, v := range f.data {
tpe, _ := f.GetType(v.Name)
if to == tpe {
switch v.Name {
case "date":
d, err := time.Parse(RFC3339FullDate, data)
if err != nil {
return nil, err
}
return Date(d), nil
case "datetime":
input := data
if len(input) == 0 {
return nil, fmt.Errorf("empty string is an invalid datetime format")
}
return ParseDateTime(input)
case "duration":
dur, err := ParseDuration(data)
if err != nil {
return nil, err
}
return Duration(dur), nil
case "uri":
return URI(data), nil
case "email":
return Email(data), nil
case "uuid":
return UUID(data), nil
case "uuid3":
return UUID3(data), nil
case "uuid4":
return UUID4(data), nil
case "uuid5":
return UUID5(data), nil
case "hostname":
return Hostname(data), nil
case "ipv4":
return IPv4(data), nil
case "ipv6":
return IPv6(data), nil
case "cidr":
return CIDR(data), nil
case "mac":
return MAC(data), nil
case "isbn":
return ISBN(data), nil
case "isbn10":
return ISBN10(data), nil
case "isbn13":
return ISBN13(data), nil
case "creditcard":
return CreditCard(data), nil
case "ssn":
return SSN(data), nil
case "hexcolor":
return HexColor(data), nil
case "rgbcolor":
return RGBColor(data), nil
case "byte":
return Base64(data), nil
case "password":
return Password(data), nil
case "ulid":
ulid, err := ParseULID(data)
if err != nil {
return nil, err
}
return ulid, nil
default:
return nil, errors.InvalidTypeName(v.Name)
}
}
}
return data, nil
}
}
// Add adds a new format, return true if this was a new item instead of a replacement
func (f *defaultFormats) Add(name string, strfmt Format, validator Validator) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
tpe := reflect.TypeOf(strfmt)
if tpe.Kind() == reflect.Ptr {
tpe = tpe.Elem()
}
for i := range f.data {
v := &f.data[i]
if v.Name == nme {
v.Type = tpe
v.Validator = validator
return false
}
}
// turns out it's new after all
f.data = append(f.data, knownFormat{Name: nme, OrigName: name, Type: tpe, Validator: validator})
return true
}
// GetType gets the type for the specified name
func (f *defaultFormats) GetType(name string) (reflect.Type, bool) {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
return v.Type, true
}
}
return nil, false
}
// DelByName removes the format by the specified name, returns true when an item was actually removed
func (f *defaultFormats) DelByName(name string) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for i, v := range f.data {
if v.Name == nme {
f.data[i] = knownFormat{} // release
f.data = append(f.data[:i], f.data[i+1:]...)
return true
}
}
return false
}
// DelByFormat removes the specified format, returns true when an item was actually removed
func (f *defaultFormats) DelByFormat(strfmt Format) bool {
f.Lock()
defer f.Unlock()
tpe := reflect.TypeOf(strfmt)
if tpe.Kind() == reflect.Ptr {
tpe = tpe.Elem()
}
for i, v := range f.data {
if v.Type == tpe {
f.data[i] = knownFormat{} // release
f.data = append(f.data[:i], f.data[i+1:]...)
return true
}
}
return false
}
// ContainsName returns true if this registry contains the specified name
func (f *defaultFormats) ContainsName(name string) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
return true
}
}
return false
}
// ContainsFormat returns true if this registry contains the specified format
func (f *defaultFormats) ContainsFormat(strfmt Format) bool {
f.Lock()
defer f.Unlock()
tpe := reflect.TypeOf(strfmt)
if tpe.Kind() == reflect.Ptr {
tpe = tpe.Elem()
}
for _, v := range f.data {
if v.Type == tpe {
return true
}
}
return false
}
// Validates passed data against format.
//
// Note that the format name is automatically normalized, e.g. one may
// use "date-time" to use the "datetime" format validator.
func (f *defaultFormats) Validates(name, data string) bool {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
return v.Validator(data)
}
}
return false
}
// Parse a string into the appropriate format representation type.
//
// E.g. parsing a string a "date" will return a Date type.
func (f *defaultFormats) Parse(name, data string) (interface{}, error) {
f.Lock()
defer f.Unlock()
nme := f.normalizeName(name)
for _, v := range f.data {
if v.Name == nme {
nw := reflect.New(v.Type).Interface()
if dec, ok := nw.(encoding.TextUnmarshaler); ok {
if err := dec.UnmarshalText([]byte(data)); err != nil {
return nil, err
}
return nw, nil
}
return nil, errors.InvalidTypeName(name)
}
}
return nil, errors.InvalidTypeName(name)
}

306
strfmt/format_test.go Normal file
View File

@ -0,0 +1,306 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"strings"
"testing"
"time"
"github.com/mitchellh/mapstructure"
"github.com/stretchr/testify/assert"
)
type testFormat string
func (t testFormat) MarshalText() ([]byte, error) {
return []byte(string(t)), nil
}
func (t *testFormat) UnmarshalText(b []byte) error {
*t = testFormat(string(b))
return nil
}
func (t testFormat) String() string {
return string(t)
}
func isTestFormat(s string) bool {
return strings.HasPrefix(s, "tf")
}
type tf2 string
func (t tf2) MarshalText() ([]byte, error) {
return []byte(string(t)), nil
}
func (t *tf2) UnmarshalText(b []byte) error {
*t = tf2(string(b))
return nil
}
func istf2(s string) bool {
return strings.HasPrefix(s, "af")
}
func (t tf2) String() string {
return string(t)
}
type bf string
func (t bf) MarshalText() ([]byte, error) {
return []byte(string(t)), nil
}
func (t *bf) UnmarshalText(b []byte) error {
*t = bf(string(b))
return nil
}
func (t bf) String() string {
return string(t)
}
func isbf(s string) bool {
return strings.HasPrefix(s, "bf")
}
func istf3(s string) bool {
return strings.HasPrefix(s, "ff")
}
func init() {
tf := testFormat("")
Default.Add("test-format", &tf, isTestFormat)
}
func TestFormatRegistry(t *testing.T) {
f2 := tf2("")
f3 := bf("")
registry := NewFormats()
assert.True(t, registry.ContainsName("test-format"))
assert.True(t, registry.ContainsName("testformat"))
assert.False(t, registry.ContainsName("ttt"))
assert.True(t, registry.Validates("testformat", "tfa"))
assert.False(t, registry.Validates("testformat", "ffa"))
assert.True(t, registry.Add("tf2", &f2, istf2))
assert.True(t, registry.ContainsName("tf2"))
assert.False(t, registry.ContainsName("tfw"))
assert.True(t, registry.Validates("tf2", "afa"))
assert.False(t, registry.Add("tf2", &f3, isbf))
assert.True(t, registry.ContainsName("tf2"))
assert.False(t, registry.ContainsName("tfw"))
assert.True(t, registry.Validates("tf2", "bfa"))
assert.False(t, registry.Validates("tf2", "afa"))
assert.False(t, registry.Add("tf2", &f2, istf2))
assert.True(t, registry.Add("tf3", &f2, istf3))
assert.True(t, registry.ContainsName("tf3"))
assert.True(t, registry.ContainsName("tf2"))
assert.False(t, registry.ContainsName("tfw"))
assert.True(t, registry.Validates("tf3", "ffa"))
assert.True(t, registry.DelByName("tf3"))
assert.True(t, registry.Add("tf3", &f2, istf3))
assert.True(t, registry.DelByName("tf3"))
assert.False(t, registry.DelByName("unknown"))
assert.False(t, registry.Validates("unknown", ""))
}
type testStruct struct {
D Date `json:"d,omitempty"`
DT DateTime `json:"dt,omitempty"`
Dur Duration `json:"dur,omitempty"`
URI URI `json:"uri,omitempty"`
Eml Email `json:"eml,omitempty"`
UUID UUID `json:"uuid,omitempty"`
UUID3 UUID3 `json:"uuid3,omitempty"`
UUID4 UUID4 `json:"uuid4,omitempty"`
UUID5 UUID5 `json:"uuid5,omitempty"`
Hn Hostname `json:"hn,omitempty"`
Ipv4 IPv4 `json:"ipv4,omitempty"`
Ipv6 IPv6 `json:"ipv6,omitempty"`
Cidr CIDR `json:"cidr,omitempty"`
Mac MAC `json:"mac,omitempty"`
Isbn ISBN `json:"isbn,omitempty"`
Isbn10 ISBN10 `json:"isbn10,omitempty"`
Isbn13 ISBN13 `json:"isbn13,omitempty"`
Creditcard CreditCard `json:"creditcard,omitempty"`
Ssn SSN `json:"ssn,omitempty"`
Hexcolor HexColor `json:"hexcolor,omitempty"`
Rgbcolor RGBColor `json:"rgbcolor,omitempty"`
B64 Base64 `json:"b64,omitempty"`
Pw Password `json:"pw,omitempty"`
ULID ULID `json:"ulid,omitempty"`
}
func TestDecodeHook(t *testing.T) {
registry := NewFormats()
m := map[string]interface{}{
"d": "2014-12-15",
"dt": "2012-03-02T15:06:05.999999999Z",
"dur": "5s",
"uri": "http://www.dummy.com",
"eml": "dummy@dummy.com",
"uuid": "a8098c1a-f86e-11da-bd1a-00112444be1e",
"uuid3": "bcd02e22-68f0-3046-a512-327cca9def8f",
"uuid4": "025b0d74-00a2-4048-bf57-227c5111bb34",
"uuid5": "886313e1-3b8a-5372-9b90-0c9aee199e5d",
"hn": "somewhere.com",
"ipv4": "192.168.254.1",
"ipv6": "::1",
"cidr": "192.0.2.1/24",
"mac": "01:02:03:04:05:06",
"isbn": "0321751043",
"isbn10": "0321751043",
"isbn13": "978-0321751041",
"hexcolor": "#FFFFFF",
"rgbcolor": "rgb(255,255,255)",
"pw": "super secret stuff here",
"ssn": "111-11-1111",
"creditcard": "4111-1111-1111-1111",
"b64": "ZWxpemFiZXRocG9zZXk=",
"ulid": "7ZZZZZZZZZZZZZZZZZZZZZZZZZ",
}
date, _ := time.Parse(RFC3339FullDate, "2014-12-15")
dur, _ := ParseDuration("5s")
dt, _ := ParseDateTime("2012-03-02T15:06:05.999999999Z")
ulid, _ := ParseULID("7ZZZZZZZZZZZZZZZZZZZZZZZZZ")
exp := &testStruct{
D: Date(date),
DT: dt,
Dur: Duration(dur),
URI: URI("http://www.dummy.com"),
Eml: Email("dummy@dummy.com"),
UUID: UUID("a8098c1a-f86e-11da-bd1a-00112444be1e"),
UUID3: UUID3("bcd02e22-68f0-3046-a512-327cca9def8f"),
UUID4: UUID4("025b0d74-00a2-4048-bf57-227c5111bb34"),
UUID5: UUID5("886313e1-3b8a-5372-9b90-0c9aee199e5d"),
Hn: Hostname("somewhere.com"),
Ipv4: IPv4("192.168.254.1"),
Ipv6: IPv6("::1"),
Cidr: CIDR("192.0.2.1/24"),
Mac: MAC("01:02:03:04:05:06"),
Isbn: ISBN("0321751043"),
Isbn10: ISBN10("0321751043"),
Isbn13: ISBN13("978-0321751041"),
Creditcard: CreditCard("4111-1111-1111-1111"),
Ssn: SSN("111-11-1111"),
Hexcolor: HexColor("#FFFFFF"),
Rgbcolor: RGBColor("rgb(255,255,255)"),
B64: Base64("ZWxpemFiZXRocG9zZXk="),
Pw: Password("super secret stuff here"),
ULID: ulid,
}
test := new(testStruct)
cfg := &mapstructure.DecoderConfig{
DecodeHook: registry.MapStructureHookFunc(),
// weakly typed will pass if this passes
WeaklyTypedInput: false,
Result: test,
}
d, err := mapstructure.NewDecoder(cfg)
assert.Nil(t, err)
err = d.Decode(m)
assert.Nil(t, err)
assert.Equal(t, exp, test)
}
func TestDecodeDateTimeHook(t *testing.T) {
testCases := []struct {
Name string
Input string
}{
{
"empty datetime",
"",
},
{
"invalid non empty datetime",
"2019-01-01",
},
}
registry := NewFormats()
type layout struct {
DateTime *DateTime `json:"datetime,omitempty"`
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.Name, func(t *testing.T) {
test := new(layout)
cfg := &mapstructure.DecoderConfig{
DecodeHook: registry.MapStructureHookFunc(),
WeaklyTypedInput: false,
Result: test,
}
d, err := mapstructure.NewDecoder(cfg)
assert.Nil(t, err)
input := make(map[string]interface{})
input["datetime"] = tc.Input
err = d.Decode(input)
assert.Error(t, err, "error expected got none")
})
}
}
func TestDecode_ULID_Hook_Negative(t *testing.T) {
t.Parallel()
testCases := []struct {
Name string
Input string
}{
{
"empty string for ulid",
"",
},
{
"invalid non empty ulid",
"8000000000YYYYYYYYYYYYYYYY",
},
}
registry := NewFormats()
type layout struct {
ULID *ULID `json:"ulid,omitempty"`
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
test := new(layout)
cfg := &mapstructure.DecoderConfig{
DecodeHook: registry.MapStructureHookFunc(),
WeaklyTypedInput: false,
Result: test,
}
d, err := mapstructure.NewDecoder(cfg)
assert.NoError(t, err)
input := make(map[string]interface{})
input["ulid"] = tc.Input
err = d.Decode(input)
assert.Error(t, err, "error expected got none")
})
}
}

13
strfmt/go.mod Normal file
View File

@ -0,0 +1,13 @@
module github.com/decentral1se/strfmt
require (
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/go-openapi/errors v0.20.2
github.com/google/uuid v1.1.1
github.com/mitchellh/mapstructure v1.3.3
github.com/oklog/ulid v1.3.1
github.com/stretchr/testify v1.8.0
go.mongodb.org/mongo-driver v1.10.0
)
go 1.13

63
strfmt/go.sum Normal file
View File

@ -0,0 +1,63 @@
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

20
strfmt/hack/coverage Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
set -e -o pipefail
# Run test coverage on each subdirectories and merge the coverage profile.
echo "mode: ${GOCOVMODE-atomic}" > coverage.txt
# Standard go tooling behavior is to ignore dirs with leading underscores
# skip generator for race detection and coverage
for dir in $(go list ./...)
do
pth="$GOPATH/src/$dir"
go test -race -timeout 20m -covermode=${GOCOVMODE-atomic} -coverprofile=${pth}/profile.out $dir
if [ -f $pth/profile.out ]
then
cat $pth/profile.out | tail -n +2 >> coverage.txt
rm $pth/profile.out
fi
done
go tool cover -func coverage.txt

297
strfmt/time.go Normal file
View File

@ -0,0 +1,297 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"database/sql/driver"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
)
func init() {
dt := DateTime{}
Default.Add("datetime", &dt, IsDateTime)
}
// IsDateTime returns true when the string is a valid date-time
func IsDateTime(str string) bool {
if len(str) < 4 {
return false
}
s := strings.Split(strings.ToLower(str), "t")
if len(s) < 2 || !IsDate(s[0]) {
return false
}
matches := rxDateTime.FindAllStringSubmatch(s[1], -1)
if len(matches) == 0 || len(matches[0]) == 0 {
return false
}
m := matches[0]
res := m[1] <= "23" && m[2] <= "59" && m[3] <= "59"
return res
}
const (
// RFC3339Millis represents a ISO8601 format to millis instead of to nanos
RFC3339Millis = "2006-01-02T15:04:05.000Z07:00"
// RFC3339MillisNoColon represents a ISO8601 format to millis instead of to nanos
RFC3339MillisNoColon = "2006-01-02T15:04:05.000Z0700"
// RFC3339Micro represents a ISO8601 format to micro instead of to nano
RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00"
// RFC3339MicroNoColon represents a ISO8601 format to micro instead of to nano
RFC3339MicroNoColon = "2006-01-02T15:04:05.000000Z0700"
// ISO8601LocalTime represents a ISO8601 format to ISO8601 in local time (no timezone)
ISO8601LocalTime = "2006-01-02T15:04:05"
// ISO8601PlusTimezone represents a ISO8601 format to ISO8601 with timezone
ISO8601PlusTimezone = "2006-01-02T15:04:05+0100"
// ISO8601TimeWithReducedPrecision represents a ISO8601 format with reduced precision (dropped secs)
ISO8601TimeWithReducedPrecision = "2006-01-02T15:04Z"
// ISO8601TimeWithReducedPrecisionLocaltime represents a ISO8601 format with reduced precision and no timezone (dropped seconds + no timezone)
ISO8601TimeWithReducedPrecisionLocaltime = "2006-01-02T15:04"
// ISO8601TimeUniversalSortableDateTimePattern represents a ISO8601 universal sortable date time pattern.
ISO8601TimeUniversalSortableDateTimePattern = "2006-01-02 15:04:05"
// DateTimePattern pattern to match for the date-time format from http://tools.ietf.org/html/rfc3339#section-5.6
DateTimePattern = `^([0-9]{2}):([0-9]{2}):([0-9]{2})(.[0-9]+)?(z|([+-][0-9]{2}:[0-9]{2}))$`
)
var (
rxDateTime = regexp.MustCompile(DateTimePattern)
// DateTimeFormats is the collection of formats used by ParseDateTime()
DateTimeFormats = []string{RFC3339Micro, RFC3339MicroNoColon, RFC3339Millis, RFC3339MillisNoColon, time.RFC3339, time.RFC3339Nano, ISO8601LocalTime, ISO8601PlusTimezone, ISO8601TimeWithReducedPrecision, ISO8601TimeWithReducedPrecisionLocaltime, ISO8601TimeUniversalSortableDateTimePattern}
// MarshalFormat sets the time resolution format used for marshaling time (set to milliseconds)
// MarshalFormat = RFC3339Millis
MarshalFormat = ISO8601LocalTime
// NormalizeTimeForMarshal provides a normalization function on time befeore marshalling (e.g. time.UTC).
// By default, the time value is not changed.
NormalizeTimeForMarshal = func(t time.Time) time.Time { return t }
)
// ParseDateTime parses a string that represents an ISO8601 time or a unix epoch
func ParseDateTime(data string) (DateTime, error) {
if data == "" {
return NewDateTime(), nil
}
var lastError error
for _, layout := range DateTimeFormats {
dd, err := time.Parse(layout, data)
if err != nil {
lastError = err
continue
}
return DateTime(dd), nil
}
return DateTime{}, lastError
}
// DateTime is a time but it serializes to ISO8601 format with millis
// It knows how to read 3 different variations of a RFC3339 date time.
// Most APIs we encounter want either millisecond or second precision times.
// This just tries to make it worry-free.
//
// swagger:strfmt date-time
type DateTime time.Time
// NewDateTime is a representation of zero value for DateTime type
func NewDateTime() DateTime {
return DateTime(time.Unix(0, 0).UTC())
}
// String converts this time to a string
func (t DateTime) String() string {
return NormalizeTimeForMarshal(time.Time(t)).Format(MarshalFormat)
}
// MarshalText implements the text marshaller interface
func (t DateTime) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
// UnmarshalText implements the text unmarshaller interface
func (t *DateTime) UnmarshalText(text []byte) error {
tt, err := ParseDateTime(string(text))
if err != nil {
return err
}
*t = tt
return nil
}
// Scan scans a DateTime value from database driver type.
func (t *DateTime) Scan(raw interface{}) error {
// TODO: case int64: and case float64: ?
switch v := raw.(type) {
case []byte:
return t.UnmarshalText(v)
case string:
return t.UnmarshalText([]byte(v))
case time.Time:
*t = DateTime(v)
case nil:
*t = DateTime{}
default:
return fmt.Errorf("cannot sql.Scan() strfmt.DateTime from: %#v", v)
}
return nil
}
// Value converts DateTime to a primitive value ready to written to a database.
func (t DateTime) Value() (driver.Value, error) {
return driver.Value(t.String()), nil
}
// MarshalJSON returns the DateTime as JSON
func (t DateTime) MarshalJSON() ([]byte, error) {
return json.Marshal(NormalizeTimeForMarshal(time.Time(t)).Format(MarshalFormat))
}
// UnmarshalJSON sets the DateTime from JSON
func (t *DateTime) UnmarshalJSON(data []byte) error {
if string(data) == jsonNull {
return nil
}
var tstr string
if err := json.Unmarshal(data, &tstr); err != nil {
return err
}
tt, err := ParseDateTime(tstr)
if err != nil {
return err
}
*t = tt
return nil
}
// MarshalBSON renders the DateTime as a BSON document
func (t DateTime) MarshalBSON() ([]byte, error) {
return bson.Marshal(bson.M{"data": t})
}
// UnmarshalBSON reads the DateTime from a BSON document
func (t *DateTime) UnmarshalBSON(data []byte) error {
var obj struct {
Data DateTime
}
if err := bson.Unmarshal(data, &obj); err != nil {
return err
}
*t = obj.Data
return nil
}
// MarshalBSONValue is an interface implemented by types that can marshal themselves
// into a BSON document represented as bytes. The bytes returned must be a valid
// BSON document if the error is nil.
// Marshals a DateTime as a bsontype.DateTime, an int64 representing
// milliseconds since epoch.
func (t DateTime) MarshalBSONValue() (bsontype.Type, []byte, error) {
// UnixNano cannot be used directly, the result of calling UnixNano on the zero
// Time is undefined. Thats why we use time.Nanosecond() instead.
tNorm := NormalizeTimeForMarshal(time.Time(t))
i64 := tNorm.Unix()*1000 + int64(tNorm.Nanosecond())/1e6
buf := make([]byte, 8)
binary.LittleEndian.PutUint64(buf, uint64(i64))
return bsontype.DateTime, buf, nil
}
// UnmarshalBSONValue is an interface implemented by types that can unmarshal a
// BSON value representation of themselves. The BSON bytes and type can be
// assumed to be valid. UnmarshalBSONValue must copy the BSON value bytes if it
// wishes to retain the data after returning.
func (t *DateTime) UnmarshalBSONValue(tpe bsontype.Type, data []byte) error {
if tpe == bsontype.Null {
*t = DateTime{}
return nil
}
if len(data) != 8 {
return errors.New("bson date field length not exactly 8 bytes")
}
i64 := int64(binary.LittleEndian.Uint64(data))
// TODO: Use bsonprim.DateTime.Time() method
*t = DateTime(time.Unix(i64/1000, i64%1000*1000000))
return nil
}
// DeepCopyInto copies the receiver and writes its value into out.
func (t *DateTime) DeepCopyInto(out *DateTime) {
*out = *t
}
// DeepCopy copies the receiver into a new DateTime.
func (t *DateTime) DeepCopy() *DateTime {
if t == nil {
return nil
}
out := new(DateTime)
t.DeepCopyInto(out)
return out
}
// GobEncode implements the gob.GobEncoder interface.
func (t DateTime) GobEncode() ([]byte, error) {
return t.MarshalBinary()
}
// GobDecode implements the gob.GobDecoder interface.
func (t *DateTime) GobDecode(data []byte) error {
return t.UnmarshalBinary(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (t DateTime) MarshalBinary() ([]byte, error) {
return NormalizeTimeForMarshal(time.Time(t)).MarshalBinary()
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (t *DateTime) UnmarshalBinary(data []byte) error {
var original time.Time
err := original.UnmarshalBinary(data)
if err != nil {
return err
}
*t = DateTime(original)
return nil
}
// Equal checks if two DateTime instances are equal using time.Time's Equal method
func (t DateTime) Equal(t2 DateTime) bool {
return time.Time(t).Equal(time.Time(t2))
}

326
strfmt/time_test.go Normal file
View File

@ -0,0 +1,326 @@
// Copyright 2015 go-swagger maintainers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package strfmt
import (
"bytes"
"encoding/gob"
"testing"
"time"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
)
var (
p, _ = time.Parse(time.RFC3339Nano, "2011-08-18T19:03:37.000000000+01:00")
testCases = []struct {
in []byte // externally sourced data -- to be unmarshalled
time time.Time // its representation in time.Time
str string // its marshalled representation
utcStr string // the marshaled representation as utc
}{
{[]byte("2014-12-15 08:00:00"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"},
{[]byte("2014-12-15T08:00:00"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"},
{[]byte("2014-12-15T08:00"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"},
{[]byte("2014-12-15T08:00Z"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"},
{[]byte("2018-01-28T23:54Z"), time.Date(2018, 01, 28, 23, 54, 0, 0, time.UTC), "2018-01-28T23:54:00.000Z", "2018-01-28T23:54:00.000Z"},
{[]byte("2014-12-15T08:00:00.000Z"), time.Date(2014, 12, 15, 8, 0, 0, 0, time.UTC), "2014-12-15T08:00:00.000Z", "2014-12-15T08:00:00.000Z"},
{[]byte("2011-08-18T19:03:37.123000000+01:00"), time.Date(2011, 8, 18, 19, 3, 37, 123*1e6, p.Location()), "2011-08-18T19:03:37.123+01:00", "2011-08-18T18:03:37.123Z"},
{[]byte("2011-08-18T19:03:37.123000+0100"), time.Date(2011, 8, 18, 19, 3, 37, 123*1e6, p.Location()), "2011-08-18T19:03:37.123+01:00", "2011-08-18T18:03:37.123Z"},
{[]byte("2011-08-18T19:03:37.123+0100"), time.Date(2011, 8, 18, 19, 3, 37, 123*1e6, p.Location()), "2011-08-18T19:03:37.123+01:00", "2011-08-18T18:03:37.123Z"},
{[]byte("2014-12-15T19:30:20Z"), time.Date(2014, 12, 15, 19, 30, 20, 0, time.UTC), "2014-12-15T19:30:20.000Z", "2014-12-15T19:30:20.000Z"},
{[]byte("0001-01-01T00:00:00Z"), time.Time{}.UTC(), "0001-01-01T00:00:00.000Z", "0001-01-01T00:00:00.000Z"},
{[]byte(""), time.Unix(0, 0).UTC(), "1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.000Z"},
{[]byte(nil), time.Unix(0, 0).UTC(), "1970-01-01T00:00:00.000Z", "1970-01-01T00:00:00.000Z"},
}
)
func TestNewDateTime(t *testing.T) {
assert.EqualValues(t, time.Unix(0, 0).UTC(), NewDateTime())
}
func TestParseDateTime_errorCases(t *testing.T) {
_, err := ParseDateTime("yada")
assert.Error(t, err)
}
// TestParseDateTime tests the full cycle:
// parsing -> marshalling -> unmarshalling / scanning
func TestParseDateTime_fullCycle(t *testing.T) {
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
parsed, err := ParseDateTime(example.str)
assert.NoError(t, err)
assert.EqualValues(t, example.time, parsed)
mt, err := parsed.MarshalText()
assert.NoError(t, err)
assert.Equal(t, []byte(example.str), mt)
if example.str != "" {
v := IsDateTime(example.str)
assert.True(t, v)
} else {
t.Logf("IsDateTime() skipped for empty testcases")
}
pp := NewDateTime()
err = pp.UnmarshalText(mt)
assert.NoError(t, err)
assert.EqualValues(t, example.time, pp)
pp = NewDateTime()
err = pp.Scan(mt)
assert.NoError(t, err)
assert.Equal(t, DateTime(example.time), pp)
}
}
func TestDateTime_IsDateTime_errorCases(t *testing.T) {
v := IsDateTime("zor")
assert.False(t, v)
v = IsDateTime("zorg")
assert.False(t, v)
v = IsDateTime("zorgTx")
assert.False(t, v)
v = IsDateTime("1972-12-31Tx")
assert.False(t, v)
v = IsDateTime("1972-12-31T24:40:00.000Z")
assert.False(t, v)
v = IsDateTime("1972-12-31T23:63:00.000Z")
assert.False(t, v)
v = IsDateTime("1972-12-31T23:59:60.000Z")
assert.False(t, v)
}
func TestDateTime_UnmarshalText_errorCases(t *testing.T) {
pp := NewDateTime()
err := pp.UnmarshalText([]byte("yada"))
assert.Error(t, err)
err = pp.UnmarshalJSON([]byte("yada"))
assert.Error(t, err)
}
func TestDateTime_UnmarshalText(t *testing.T) {
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
pp := NewDateTime()
err := pp.UnmarshalText(example.in)
assert.NoError(t, err)
assert.EqualValues(t, example.time, pp)
// Other way around
val, erv := pp.Value()
assert.NoError(t, erv)
assert.EqualValues(t, example.str, val)
}
}
func TestDateTime_UnmarshalJSON(t *testing.T) {
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
pp := NewDateTime()
err := pp.UnmarshalJSON(esc(example.in))
assert.NoError(t, err)
assert.EqualValues(t, example.time, pp)
}
// Check UnmarshalJSON failure with no lexed items
pp := NewDateTime()
err := pp.UnmarshalJSON([]byte("zorg emperor"))
assert.Error(t, err)
// Check lexer failure
err = pp.UnmarshalJSON([]byte(`"zorg emperor"`))
assert.Error(t, err)
// Check null case
err = pp.UnmarshalJSON([]byte("null"))
assert.Nil(t, err)
}
func esc(v []byte) []byte {
var buf bytes.Buffer
buf.WriteByte('"')
buf.Write(v)
buf.WriteByte('"')
return buf.Bytes()
}
func TestDateTime_MarshalText(t *testing.T) {
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
dt := DateTime(example.time)
mt, err := dt.MarshalText()
assert.NoError(t, err)
assert.Equal(t, []byte(example.str), mt)
}
}
func TestDateTime_MarshalJSON(t *testing.T) {
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
dt := DateTime(example.time)
bb, err := dt.MarshalJSON()
assert.NoError(t, err)
assert.EqualValues(t, esc([]byte(example.str)), bb)
}
}
func TestDateTime_MarshalJSON_Override(t *testing.T) {
oldNormalizeMarshal := NormalizeTimeForMarshal
defer func() {
NormalizeTimeForMarshal = oldNormalizeMarshal
}()
NormalizeTimeForMarshal = func(t time.Time) time.Time {
return t.UTC()
}
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
dt := DateTime(example.time.UTC())
bb, err := dt.MarshalJSON()
assert.NoError(t, err)
assert.EqualValues(t, esc([]byte(example.utcStr)), bb)
}
}
func TestDateTime_Scan(t *testing.T) {
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
pp := NewDateTime()
err := pp.Scan(example.in)
assert.NoError(t, err)
assert.Equal(t, DateTime(example.time), pp)
pp = NewDateTime()
err = pp.Scan(string(example.in))
assert.NoError(t, err)
assert.Equal(t, DateTime(example.time), pp)
pp = NewDateTime()
err = pp.Scan(example.time)
assert.NoError(t, err)
assert.Equal(t, DateTime(example.time), pp)
}
}
func TestDateTime_Scan_Failed(t *testing.T) {
pp := NewDateTime()
zero := NewDateTime()
err := pp.Scan(nil)
assert.NoError(t, err)
// Zero values differ...
// assert.Equal(t, zero, pp)
assert.Equal(t, DateTime{}, pp)
err = pp.Scan("")
assert.NoError(t, err)
assert.Equal(t, zero, pp)
err = pp.Scan(int64(0))
assert.Error(t, err)
err = pp.Scan(float64(0))
assert.Error(t, err)
}
func TestDateTime_BSON(t *testing.T) {
for caseNum, example := range testCases {
t.Logf("Case #%d", caseNum)
dt := DateTime(example.time)
bsonData, err := bson.Marshal(&dt)
assert.NoError(t, err)
var dtCopy DateTime
err = bson.Unmarshal(bsonData, &dtCopy)
assert.NoError(t, err)
// BSON DateTime type loses timezone information, so compare UTC()
assert.Equal(t, time.Time(dt).UTC(), time.Time(dtCopy).UTC())
// Check value marshaling explicitly
m := bson.M{"data": dt}
bsonData, err = bson.Marshal(&m)
assert.NoError(t, err)
var mCopy bson.M
err = bson.Unmarshal(bsonData, &mCopy)
assert.NoError(t, err)
data, ok := m["data"].(DateTime)
assert.Equal(t, true, ok)
assert.Equal(t, time.Time(dt).UTC(), time.Time(data).UTC())
}
}
func TestDeepCopyDateTime(t *testing.T) {
p, err := ParseDateTime("2011-08-18T19:03:37.000000000+01:00")
assert.NoError(t, err)
in := &p
out := new(DateTime)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *DateTime
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestGobEncodingDateTime(t *testing.T) {
now := time.Now()
b := bytes.Buffer{}
enc := gob.NewEncoder(&b)
err := enc.Encode(DateTime(now))
assert.NoError(t, err)
assert.NotEmpty(t, b.Bytes())
var result DateTime
dec := gob.NewDecoder(&b)
err = dec.Decode(&result)
assert.NoError(t, err)
assert.Equal(t, now.Year(), time.Time(result).Year())
assert.Equal(t, now.Month(), time.Time(result).Month())
assert.Equal(t, now.Day(), time.Time(result).Day())
assert.Equal(t, now.Hour(), time.Time(result).Hour())
assert.Equal(t, now.Minute(), time.Time(result).Minute())
assert.Equal(t, now.Second(), time.Time(result).Second())
}
func TestDateTime_Equal(t *testing.T) {
t.Parallel()
dt1 := DateTime(time.Now())
dt2 := DateTime(time.Time(dt1).Add(time.Second))
//nolint:gocritic
assert.True(t, dt1.Equal(dt1), "DateTime instances should be equal")
assert.False(t, dt1.Equal(dt2), "DateTime instances should not be equal")
}

225
strfmt/ulid.go Normal file
View File

@ -0,0 +1,225 @@
package strfmt
import (
cryptorand "crypto/rand"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"io"
"sync"
"github.com/oklog/ulid"
"go.mongodb.org/mongo-driver/bson"
)
// ULID represents a ulid string format
// ref:
// https://github.com/ulid/spec
// impl:
// https://github.com/oklog/ulid
//
// swagger:strfmt ulid
type ULID struct {
ulid.ULID
}
var (
ulidEntropyPool = sync.Pool{
New: func() interface{} {
return cryptorand.Reader
},
}
ULIDScanDefaultFunc = func(raw interface{}) (ULID, error) {
u := NewULIDZero()
switch x := raw.(type) {
case nil:
// zerp ulid
return u, nil
case string:
if x == "" {
// zero ulid
return u, nil
}
return u, u.UnmarshalText([]byte(x))
case []byte:
return u, u.UnmarshalText(x)
}
return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v: %w", raw, ulid.ErrScanValue)
}
// ULIDScanOverrideFunc allows you to override the Scan method of the ULID type
ULIDScanOverrideFunc = ULIDScanDefaultFunc
ULIDValueDefaultFunc = func(u ULID) (driver.Value, error) {
return driver.Value(u.String()), nil
}
// ULIDValueOverrideFunc allows you to override the Value method of the ULID type
ULIDValueOverrideFunc = ULIDValueDefaultFunc
)
func init() {
// register formats in the default registry:
// - ulid
ulid := ULID{}
Default.Add("ulid", &ulid, IsULID)
}
// IsULID checks if provided string is ULID format
// Be noticed that this function considers overflowed ULID as non-ulid.
// For more details see https://github.com/ulid/spec
func IsULID(str string) bool {
_, err := ulid.ParseStrict(str)
return err == nil
}
// ParseULID parses a string that represents an valid ULID
func ParseULID(str string) (ULID, error) {
var u ULID
return u, u.UnmarshalText([]byte(str))
}
// NewULIDZero returns a zero valued ULID type
func NewULIDZero() ULID {
return ULID{}
}
// NewULID generates new unique ULID value and a error if any
func NewULID() (u ULID, err error) {
obj := ulidEntropyPool.Get()
entropy, ok := obj.(io.Reader)
if !ok {
return u, fmt.Errorf("failed to cast %+v to io.Reader", obj)
}
id, err := ulid.New(ulid.Now(), entropy)
if err != nil {
return u, err
}
ulidEntropyPool.Put(entropy)
u.ULID = id
return u, nil
}
// GetULID returns underlying instance of ULID
func (u *ULID) GetULID() interface{} {
return u.ULID
}
// MarshalText returns this instance into text
func (u ULID) MarshalText() ([]byte, error) {
return u.ULID.MarshalText()
}
// UnmarshalText hydrates this instance from text
func (u *ULID) UnmarshalText(data []byte) error { // validation is performed later on
return u.ULID.UnmarshalText(data)
}
// Scan reads a value from a database driver
func (u *ULID) Scan(raw interface{}) error {
ul, err := ULIDScanOverrideFunc(raw)
if err == nil {
*u = ul
}
return err
}
// Value converts a value to a database driver value
func (u ULID) Value() (driver.Value, error) {
return ULIDValueOverrideFunc(u)
}
func (u ULID) String() string {
return u.ULID.String()
}
// MarshalJSON returns the ULID as JSON
func (u ULID) MarshalJSON() ([]byte, error) {
return json.Marshal(u.String())
}
// UnmarshalJSON sets the ULID from JSON
func (u *ULID) UnmarshalJSON(data []byte) error {
if string(data) == jsonNull {
return nil
}
var ustr string
if err := json.Unmarshal(data, &ustr); err != nil {
return err
}
id, err := ulid.ParseStrict(ustr)
if err != nil {
return fmt.Errorf("couldn't parse JSON value as ULID: %w", err)
}
u.ULID = id
return nil
}
// MarshalBSON document from this value
func (u ULID) MarshalBSON() ([]byte, error) {
return bson.Marshal(bson.M{"data": u.String()})
}
// UnmarshalBSON document into this value
func (u *ULID) UnmarshalBSON(data []byte) error {
var m bson.M
if err := bson.Unmarshal(data, &m); err != nil {
return err
}
if ud, ok := m["data"].(string); ok {
id, err := ulid.ParseStrict(ud)
if err != nil {
return fmt.Errorf("couldn't parse bson bytes as ULID: %w", err)
}
u.ULID = id
return nil
}
return errors.New("couldn't unmarshal bson bytes as ULID")
}
// DeepCopyInto copies the receiver and writes its value into out.
func (u *ULID) DeepCopyInto(out *ULID) {
*out = *u
}
// DeepCopy copies the receiver into a new ULID.
func (u *ULID) DeepCopy() *ULID {
if u == nil {
return nil
}
out := new(ULID)
u.DeepCopyInto(out)
return out
}
// GobEncode implements the gob.GobEncoder interface.
func (u ULID) GobEncode() ([]byte, error) {
return u.ULID.MarshalBinary()
}
// GobDecode implements the gob.GobDecoder interface.
func (u *ULID) GobDecode(data []byte) error {
return u.ULID.UnmarshalBinary(data)
}
// MarshalBinary implements the encoding.BinaryMarshaler interface.
func (u ULID) MarshalBinary() ([]byte, error) {
return u.ULID.MarshalBinary()
}
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
func (u *ULID) UnmarshalBinary(data []byte) error {
return u.ULID.UnmarshalBinary(data)
}
// Equal checks if two ULID instances are equal by their underlying type
func (u ULID) Equal(other ULID) bool {
return u.ULID == other.ULID
}

343
strfmt/ulid_test.go Normal file
View File

@ -0,0 +1,343 @@
package strfmt
import (
"bytes"
"database/sql/driver"
"encoding/gob"
"fmt"
"sync"
"testing"
"github.com/stretchr/testify/assert"
"go.mongodb.org/mongo-driver/bson"
)
const testUlid = string("01EYXZVGBHG26MFTG4JWR4K558")
const testUlidAlt = string("01EYXZW663G7PYHVSQ8WTMDA67")
var testUlidOverrideMtx sync.Mutex
var testUlidOverrideValMtx sync.Mutex
func TestFormatULID_Text(t *testing.T) {
t.Parallel()
t.Run("positive", func(t *testing.T) {
t.Parallel()
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
res, err := ulid.MarshalText()
assert.NoError(t, err)
assert.Equal(t, testUlid, string(res))
ulid2, _ := ParseULID(testUlidAlt)
assert.NoError(t, err)
what := []byte(testUlid)
err = ulid2.UnmarshalText(what)
assert.NoError(t, err)
assert.Equal(t, testUlid, ulid2.String())
})
t.Run("negative", func(t *testing.T) {
t.Parallel()
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
what := []byte("00000000-0000-0000-0000-000000000000")
err = ulid.UnmarshalText(what)
assert.Error(t, err)
})
}
func TestFormatULID_BSON(t *testing.T) {
t.Parallel()
t.Run("positive", func(t *testing.T) {
t.Parallel()
ulid, _ := ParseULID(testUlid)
bsonData, err := bson.Marshal(&ulid)
assert.NoError(t, err)
var ulidUnmarshaled ULID
err = bson.Unmarshal(bsonData, &ulidUnmarshaled)
assert.NoError(t, err)
assert.Equal(t, ulid, ulidUnmarshaled)
// Check value marshaling explicitly
m := bson.M{"data": ulid}
bsonData, err = bson.Marshal(&m)
assert.NoError(t, err)
var mUnmarshaled bson.M
err = bson.Unmarshal(bsonData, &mUnmarshaled)
assert.NoError(t, err)
data, ok := m["data"].(ULID)
assert.Equal(t, true, ok)
assert.Equal(t, ulid, data)
})
t.Run("negative", func(t *testing.T) {
t.Parallel()
uuid := UUID("00000000-0000-0000-0000-000000000000")
bsonData, err := bson.Marshal(&uuid)
assert.NoError(t, err)
var ulidUnmarshaled ULID
err = bson.Unmarshal(bsonData, &ulidUnmarshaled)
assert.Error(t, err)
})
}
func TestFormatULID_JSON(t *testing.T) {
t.Parallel()
t.Run("positive", func(t *testing.T) {
t.Parallel()
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
whatStr := fmt.Sprintf(`"%s"`, testUlidAlt)
what := []byte(whatStr)
err = ulid.UnmarshalJSON(what)
assert.NoError(t, err)
assert.Equal(t, testUlidAlt, ulid.String())
data, err := ulid.MarshalJSON()
assert.NoError(t, err)
assert.Equal(t, whatStr, string(data))
})
t.Run("null", func(t *testing.T) {
t.Parallel()
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
err = ulid.UnmarshalJSON([]byte("null"))
assert.Nil(t, err)
})
t.Run("negative", func(t *testing.T) {
t.Parallel()
// Check UnmarshalJSON failure with no lexed items
ulid := NewULIDZero()
err := ulid.UnmarshalJSON([]byte("zorg emperor"))
assert.Error(t, err)
// Check lexer failure
err = ulid.UnmarshalJSON([]byte(`"zorg emperor"`))
assert.Error(t, err)
})
}
func TestFormatULID_Scan(t *testing.T) {
t.Parallel()
t.Run("db.Scan", func(t *testing.T) {
t.Parallel()
testUlidOverrideMtx.Lock()
defer testUlidOverrideMtx.Unlock()
srcUlid := testUlidAlt
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
err = ulid.Scan(srcUlid)
assert.NoError(t, err)
assert.Equal(t, srcUlid, ulid.String())
ulid, _ = ParseULID(testUlid)
err = ulid.Scan([]byte(srcUlid))
assert.NoError(t, err)
assert.Equal(t, srcUlid, ulid.String())
})
t.Run("db.Scan_Failed", func(t *testing.T) {
t.Parallel()
testUlidOverrideMtx.Lock()
defer testUlidOverrideMtx.Unlock()
ulid, err := ParseULID(testUlid)
zero := NewULIDZero()
assert.NoError(t, err)
err = ulid.Scan(nil)
assert.NoError(t, err)
assert.Equal(t, zero, ulid)
err = ulid.Scan("")
assert.NoError(t, err)
assert.Equal(t, zero, ulid)
err = ulid.Scan(int64(0))
assert.Error(t, err)
err = ulid.Scan(float64(0))
assert.Error(t, err)
})
t.Run("db.Value", func(t *testing.T) {
t.Parallel()
testUlidOverrideValMtx.Lock()
defer testUlidOverrideValMtx.Unlock()
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
val, err := ulid.Value()
assert.NoError(t, err)
assert.EqualValues(t, testUlid, val)
})
t.Run("override.Scan", func(t *testing.T) {
t.Parallel()
testUlidOverrideMtx.Lock()
defer testUlidOverrideMtx.Unlock()
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
ulid2, err := ParseULID(testUlidAlt)
assert.NoError(t, err)
ULIDScanOverrideFunc = func(raw interface{}) (ULID, error) {
u := NewULIDZero()
switch x := raw.(type) {
case [16]byte:
return u, u.ULID.UnmarshalBinary(x[:])
case int: // just for linter
return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v", raw)
}
return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v", raw)
}
// get underlying binary implementation which is actually [16]byte
bytes := [16]byte(ulid.ULID)
err = ulid2.Scan(bytes)
assert.NoError(t, err)
assert.Equal(t, ulid2, ulid)
assert.Equal(t, ulid2.String(), ulid.String())
// check other default cases became unreachable
err = ulid2.Scan(testUlid)
assert.Error(t, err)
// return default Scan method
ULIDScanOverrideFunc = ULIDScanDefaultFunc
err = ulid2.Scan(testUlid)
assert.NoError(t, err)
assert.Equal(t, ulid2.String(), testUlid)
})
t.Run("override.Value", func(t *testing.T) {
t.Parallel()
testUlidOverrideValMtx.Lock()
defer testUlidOverrideValMtx.Unlock()
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
ulid2, err := ParseULID(testUlid)
assert.NoError(t, err)
ULIDValueOverrideFunc = func(u ULID) (driver.Value, error) {
bytes := [16]byte(u.ULID)
return driver.Value(bytes), nil
}
exp := [16]byte(ulid2.ULID)
val, err := ulid.Value()
assert.NoError(t, err)
assert.EqualValues(t, exp, val)
// return default Value method
ULIDValueOverrideFunc = ULIDValueDefaultFunc
val, err = ulid.Value()
assert.NoError(t, err)
assert.EqualValues(t, testUlid, val)
})
}
func TestFormatULID_DeepCopy(t *testing.T) {
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
in := &ulid
out := new(ULID)
in.DeepCopyInto(out)
assert.Equal(t, in, out)
out2 := in.DeepCopy()
assert.Equal(t, in, out2)
var inNil *ULID
out3 := inNil.DeepCopy()
assert.Nil(t, out3)
}
func TestFormatULID_GobEncoding(t *testing.T) {
ulid, err := ParseULID(testUlid)
assert.NoError(t, err)
b := bytes.Buffer{}
enc := gob.NewEncoder(&b)
err = enc.Encode(ulid)
assert.NoError(t, err)
assert.NotEmpty(t, b.Bytes())
var result ULID
dec := gob.NewDecoder(&b)
err = dec.Decode(&result)
assert.NoError(t, err)
assert.Equal(t, ulid, result)
assert.Equal(t, ulid.String(), result.String())
}
func TestFormatULID_NewULID_and_Equal(t *testing.T) {
t.Parallel()
ulid1, err := NewULID()
assert.NoError(t, err)
ulid2, err := NewULID()
assert.NoError(t, err)
//nolint:gocritic
assert.True(t, ulid1.Equal(ulid1), "ULID instances should be equal")
assert.False(t, ulid1.Equal(ulid2), "ULID instances should not be equal")
ulidZero := NewULIDZero()
ulidZero2 := NewULIDZero()
assert.True(t, ulidZero.Equal(ulidZero2), "ULID instances should be equal")
}
func TestIsULID(t *testing.T) {
t.Parallel()
tcases := []struct {
ulid string
expect bool
}{
{ulid: "01EYXZVGBHG26MFTG4JWR4K558", expect: true},
{ulid: "01EYXZW663G7PYHVSQ8WTMDA67", expect: true},
{ulid: "7ZZZZZZZZZ0000000000000000", expect: true},
{ulid: "00000000000000000000000000", expect: true},
{ulid: "7ZZZZZZZZZZZZZZZZZZZZZZZZZ", expect: true},
{ulid: "not-a-ulid", expect: false},
{ulid: "8000000000FJ2MMFJ3ATV3XB2C", expect: false},
{ulid: "81EYY0NEYJZZZZZZZZZZZZZZZZ", expect: false},
{ulid: "7ZZZZZZZZZ000000000000000U", expect: false},
{ulid: "7ZZZZZZZZZ000000000000000L", expect: false},
{ulid: "7ZZZZZZZZZ000000000000000O", expect: false},
{ulid: "7ZZZZZZZZZ000000000000000I", expect: false},
}
for _, tcase := range tcases {
tc := tcase
t.Run(fmt.Sprintf("%s:%t", tc.ulid, tc.expect), func(t *testing.T) {
t.Parallel()
if tc.expect {
assert.True(t, IsULID(tc.ulid))
} else {
assert.False(t, IsULID(tc.ulid))
}
})
}
}