diff --git a/.golangci.yml b/.golangci.yml index 22f8d21..d2fafb8 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,12 +1,6 @@ linters-settings: - govet: - check-shadowing: true - golint: - min-confidence: 0 gocyclo: min-complexity: 45 - maligned: - suggest-new: true dupl: threshold: 200 goconst: @@ -16,7 +10,7 @@ linters-settings: linters: enable-all: true disable: - - maligned + - recvcheck - unparam - lll - gochecknoinits @@ -29,9 +23,6 @@ linters: - wrapcheck - testpackage - nlreturn - - gomnd - - exhaustivestruct - - goerr113 - errorlint - nestif - godot @@ -39,7 +30,6 @@ linters: - paralleltest - tparallel - thelper - - ifshort - exhaustruct - varnamelen - gci @@ -52,10 +42,15 @@ linters: - forcetypeassert - cyclop # deprecated linters - - deadcode - - interfacer - - scopelint - - varcheck - - structcheck - - golint - - nosnakecase + #- deadcode + #- interfacer + #- scopelint + #- varcheck + #- structcheck + #- golint + #- nosnakecase + #- maligned + #- goerr113 + #- ifshort + #- gomnd + #- exhaustivestruct diff --git a/bson.go b/bson.go index cfa9a52..685eaf6 100644 --- a/bson.go +++ b/bson.go @@ -83,7 +83,7 @@ func (id *ObjectId) Scan(raw interface{}) error { case string: data = []byte(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v: %w", v, ErrFormat) } return id.UnmarshalText(data) diff --git a/date.go b/date.go index 3c93381..a8f52ff 100644 --- a/date.go +++ b/date.go @@ -17,7 +17,6 @@ package strfmt import ( "database/sql/driver" "encoding/json" - "errors" "fmt" "time" @@ -84,7 +83,7 @@ func (d *Date) Scan(raw interface{}) error { *d = Date{} return nil default: - return fmt.Errorf("cannot sql.Scan() strfmt.Date from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.Date from: %#v: %w", v, ErrFormat) } } @@ -134,7 +133,7 @@ func (d *Date) UnmarshalBSON(data []byte) error { return nil } - return errors.New("couldn't unmarshal bson bytes value as Date") + return fmt.Errorf("couldn't unmarshal bson bytes value as Date: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. diff --git a/default.go b/default.go index 2813714..eb9a852 100644 --- a/default.go +++ b/default.go @@ -18,7 +18,6 @@ import ( "database/sql/driver" "encoding/base64" "encoding/json" - "errors" "fmt" "net/mail" "regexp" @@ -96,15 +95,17 @@ func IsHostname(str string) bool { } // the sum of all label octets and label lengths is limited to 255. - if len(str) > 255 { + const maxHostnameLength = 255 + if len(str) > maxHostnameLength { return false } // Each node has a label, which is zero to 63 octets in length + const maxNodeLength = 63 parts := strings.Split(str, ".") valid := true for _, p := range parts { - if len(p) > 63 { + if len(p) > maxNodeLength { valid = false } } @@ -117,22 +118,28 @@ func IsUUID(str string) bool { return err == nil } +const ( + uuidV3 = 3 + uuidV4 = 4 + uuidV5 = 5 +) + // IsUUID3 returns true is the string matches a UUID v3, upper case is allowed func IsUUID3(str string) bool { id, err := uuid.Parse(str) - return err == nil && id.Version() == uuid.Version(3) + return err == nil && id.Version() == uuid.Version(uuidV3) } // IsUUID4 returns true is the string matches a UUID v4, upper case is allowed func IsUUID4(str string) bool { id, err := uuid.Parse(str) - return err == nil && id.Version() == uuid.Version(4) + return err == nil && id.Version() == uuid.Version(uuidV4) } // IsUUID5 returns true is the string matches a UUID v5, upper case is allowed func IsUUID5(str string) bool { id, err := uuid.Parse(str) - return err == nil && id.Version() == uuid.Version(5) + return err == nil && id.Version() == uuid.Version(uuidV5) } // IsEmail validates an email address. @@ -269,7 +276,7 @@ func (b *Base64) Scan(raw interface{}) error { } *b = Base64(vv) default: - return fmt.Errorf("cannot sql.Scan() strfmt.Base64 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.Base64 from: %#v: %w", v, ErrFormat) } return nil @@ -323,7 +330,7 @@ func (b *Base64) UnmarshalBSON(data []byte) error { *b = Base64(vb) return nil } - return errors.New("couldn't unmarshal bson bytes as base64") + return fmt.Errorf("couldn't unmarshal bson bytes as base64: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -365,7 +372,7 @@ func (u *URI) Scan(raw interface{}) error { case string: *u = URI(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.URI from: %#v: %w", v, ErrFormat) } return nil @@ -411,7 +418,7 @@ func (u *URI) UnmarshalBSON(data []byte) error { *u = URI(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as uri") + return fmt.Errorf("couldn't unmarshal bson bytes as uri: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -453,7 +460,7 @@ func (e *Email) Scan(raw interface{}) error { case string: *e = Email(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.Email from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.Email from: %#v: %w", v, ErrFormat) } return nil @@ -499,7 +506,7 @@ func (e *Email) UnmarshalBSON(data []byte) error { *e = Email(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as email") + return fmt.Errorf("couldn't unmarshal bson bytes as email: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -541,7 +548,7 @@ func (h *Hostname) Scan(raw interface{}) error { case string: *h = Hostname(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.Hostname from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.Hostname from: %#v: %w", v, ErrFormat) } return nil @@ -587,7 +594,7 @@ func (h *Hostname) UnmarshalBSON(data []byte) error { *h = Hostname(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as hostname") + return fmt.Errorf("couldn't unmarshal bson bytes as hostname: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -629,7 +636,7 @@ func (u *IPv4) Scan(raw interface{}) error { case string: *u = IPv4(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.IPv4 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.IPv4 from: %#v: %w", v, ErrFormat) } return nil @@ -675,7 +682,7 @@ func (u *IPv4) UnmarshalBSON(data []byte) error { *u = IPv4(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as ipv4") + return fmt.Errorf("couldn't unmarshal bson bytes as ipv4: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -717,7 +724,7 @@ func (u *IPv6) Scan(raw interface{}) error { case string: *u = IPv6(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.IPv6 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.IPv6 from: %#v: %w", v, ErrFormat) } return nil @@ -763,7 +770,7 @@ func (u *IPv6) UnmarshalBSON(data []byte) error { *u = IPv6(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as ipv6") + return fmt.Errorf("couldn't unmarshal bson bytes as ipv6: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -805,7 +812,7 @@ func (u *CIDR) Scan(raw interface{}) error { case string: *u = CIDR(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.CIDR from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.CIDR from: %#v: %w", v, ErrFormat) } return nil @@ -851,7 +858,7 @@ func (u *CIDR) UnmarshalBSON(data []byte) error { *u = CIDR(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as CIDR") + return fmt.Errorf("couldn't unmarshal bson bytes as CIDR: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -893,7 +900,7 @@ func (u *MAC) Scan(raw interface{}) error { case string: *u = MAC(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.IPv4 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.IPv4 from: %#v: %w", v, ErrFormat) } return nil @@ -939,7 +946,7 @@ func (u *MAC) UnmarshalBSON(data []byte) error { *u = MAC(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as MAC") + return fmt.Errorf("couldn't unmarshal bson bytes as MAC: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -981,7 +988,7 @@ func (u *UUID) Scan(raw interface{}) error { case string: *u = UUID(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.UUID from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.UUID from: %#v: %w", v, ErrFormat) } return nil @@ -1030,7 +1037,7 @@ func (u *UUID) UnmarshalBSON(data []byte) error { *u = UUID(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as UUID") + return fmt.Errorf("couldn't unmarshal bson bytes as UUID: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1072,7 +1079,7 @@ func (u *UUID3) Scan(raw interface{}) error { case string: *u = UUID3(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.UUID3 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.UUID3 from: %#v: %w", v, ErrFormat) } return nil @@ -1121,7 +1128,7 @@ func (u *UUID3) UnmarshalBSON(data []byte) error { *u = UUID3(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as UUID3") + return fmt.Errorf("couldn't unmarshal bson bytes as UUID3: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1163,7 +1170,7 @@ func (u *UUID4) Scan(raw interface{}) error { case string: *u = UUID4(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.UUID4 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.UUID4 from: %#v: %w", v, ErrFormat) } return nil @@ -1212,7 +1219,7 @@ func (u *UUID4) UnmarshalBSON(data []byte) error { *u = UUID4(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as UUID4") + return fmt.Errorf("couldn't unmarshal bson bytes as UUID4: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1254,7 +1261,7 @@ func (u *UUID5) Scan(raw interface{}) error { case string: *u = UUID5(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.UUID5 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.UUID5 from: %#v: %w", v, ErrFormat) } return nil @@ -1303,7 +1310,7 @@ func (u *UUID5) UnmarshalBSON(data []byte) error { *u = UUID5(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as UUID5") + return fmt.Errorf("couldn't unmarshal bson bytes as UUID5: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1345,7 +1352,7 @@ func (u *ISBN) Scan(raw interface{}) error { case string: *u = ISBN(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.ISBN from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.ISBN from: %#v: %w", v, ErrFormat) } return nil @@ -1394,7 +1401,7 @@ func (u *ISBN) UnmarshalBSON(data []byte) error { *u = ISBN(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as ISBN") + return fmt.Errorf("couldn't unmarshal bson bytes as ISBN: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1436,7 +1443,7 @@ func (u *ISBN10) Scan(raw interface{}) error { case string: *u = ISBN10(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.ISBN10 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.ISBN10 from: %#v: %w", v, ErrFormat) } return nil @@ -1485,7 +1492,7 @@ func (u *ISBN10) UnmarshalBSON(data []byte) error { *u = ISBN10(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as ISBN10") + return fmt.Errorf("couldn't unmarshal bson bytes as ISBN10: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1527,7 +1534,7 @@ func (u *ISBN13) Scan(raw interface{}) error { case string: *u = ISBN13(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.ISBN13 from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.ISBN13 from: %#v: %w", v, ErrFormat) } return nil @@ -1576,7 +1583,7 @@ func (u *ISBN13) UnmarshalBSON(data []byte) error { *u = ISBN13(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as ISBN13") + return fmt.Errorf("couldn't unmarshal bson bytes as ISBN13: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1618,7 +1625,7 @@ func (u *CreditCard) Scan(raw interface{}) error { case string: *u = CreditCard(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.CreditCard from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.CreditCard from: %#v: %w", v, ErrFormat) } return nil @@ -1667,7 +1674,7 @@ func (u *CreditCard) UnmarshalBSON(data []byte) error { *u = CreditCard(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as CreditCard") + return fmt.Errorf("couldn't unmarshal bson bytes as CreditCard: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1709,7 +1716,7 @@ func (u *SSN) Scan(raw interface{}) error { case string: *u = SSN(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.SSN from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.SSN from: %#v: %w", v, ErrFormat) } return nil @@ -1758,7 +1765,7 @@ func (u *SSN) UnmarshalBSON(data []byte) error { *u = SSN(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as SSN") + return fmt.Errorf("couldn't unmarshal bson bytes as SSN: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1800,7 +1807,7 @@ func (h *HexColor) Scan(raw interface{}) error { case string: *h = HexColor(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.HexColor from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.HexColor from: %#v: %w", v, ErrFormat) } return nil @@ -1849,7 +1856,7 @@ func (h *HexColor) UnmarshalBSON(data []byte) error { *h = HexColor(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as HexColor") + return fmt.Errorf("couldn't unmarshal bson bytes as HexColor: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1891,7 +1898,7 @@ func (r *RGBColor) Scan(raw interface{}) error { case string: *r = RGBColor(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.RGBColor from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.RGBColor from: %#v: %w", v, ErrFormat) } return nil @@ -1940,7 +1947,7 @@ func (r *RGBColor) UnmarshalBSON(data []byte) error { *r = RGBColor(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as RGBColor") + return fmt.Errorf("couldn't unmarshal bson bytes as RGBColor: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. @@ -1983,7 +1990,7 @@ func (r *Password) Scan(raw interface{}) error { case string: *r = Password(v) default: - return fmt.Errorf("cannot sql.Scan() strfmt.Password from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.Password from: %#v: %w", v, ErrFormat) } return nil @@ -2032,7 +2039,7 @@ func (r *Password) UnmarshalBSON(data []byte) error { *r = Password(ud) return nil } - return errors.New("couldn't unmarshal bson bytes as Password") + return fmt.Errorf("couldn't unmarshal bson bytes as Password: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. diff --git a/default_test.go b/default_test.go index 8b2e2f5..87371d8 100644 --- a/default_test.go +++ b/default_test.go @@ -440,7 +440,7 @@ func testStringFormat(t *testing.T, what testableFormat, format, with string, va // Stringer strVal = what.String() - assert.Equalf(t, []byte(with), b, "[%s]String: expected %v and %v to be equal", strVal, with) + assert.Equalf(t, []byte(with), b, "[%s]String: expected %v and %v to be equal", format, strVal, with) // JSON encoding interface bj := []byte("\"" + with + "\"") diff --git a/duration.go b/duration.go index 6284b82..0d85140 100644 --- a/duration.go +++ b/duration.go @@ -17,7 +17,6 @@ package strfmt import ( "database/sql/driver" "encoding/json" - "errors" "fmt" "regexp" "strconv" @@ -33,6 +32,11 @@ func init() { Default.Add("duration", &d, IsDuration) } +const ( + hoursInDay = 24 + daysInWeek = 7 +) + var ( timeUnits = [][]string{ {"ns", "nano"}, @@ -52,8 +56,8 @@ var ( "s": time.Second, "m": time.Minute, "h": time.Hour, - "d": 24 * time.Hour, - "w": 7 * 24 * time.Hour, + "d": hoursInDay * time.Hour, + "w": hoursInDay * daysInWeek * time.Hour, } durationMatcher = regexp.MustCompile(`((\d+)\s*([A-Za-zµ]+))`) @@ -120,7 +124,7 @@ func ParseDuration(cand string) (time.Duration, error) { if ok { return dur, nil } - return 0, fmt.Errorf("unable to parse %s as duration", cand) + return 0, fmt.Errorf("unable to parse %s as duration: %w", cand, ErrFormat) } // Scan reads a Duration value from database driver type. @@ -134,7 +138,7 @@ func (d *Duration) Scan(raw interface{}) error { case nil: *d = Duration(0) default: - return fmt.Errorf("cannot sql.Scan() strfmt.Duration from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.Duration from: %#v: %w", v, ErrFormat) } return nil @@ -192,7 +196,7 @@ func (d *Duration) UnmarshalBSON(data []byte) error { return nil } - return errors.New("couldn't unmarshal bson bytes value as Date") + return fmt.Errorf("couldn't unmarshal bson bytes value as Date: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..9a92403 --- /dev/null +++ b/errors.go @@ -0,0 +1,10 @@ +package strfmt + +type strfmtError string + +// ErrFormat is an error raised by the strfmt package +const ErrFormat strfmtError = "format error" + +func (e strfmtError) Error() string { + return string(e) +} diff --git a/format.go b/format.go index 888e107..2c45cd2 100644 --- a/format.go +++ b/format.go @@ -16,7 +16,6 @@ package strfmt import ( "encoding" - stderrors "errors" "fmt" "reflect" "strings" @@ -102,7 +101,7 @@ func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc { } data, ok := obj.(string) if !ok { - return nil, fmt.Errorf("failed to cast %+v to string", obj) + return nil, fmt.Errorf("failed to cast %+v to string: %w", obj, ErrFormat) } for _, v := range f.data { @@ -118,7 +117,7 @@ func (f *defaultFormats) MapStructureHookFunc() mapstructure.DecodeHookFunc { case "datetime": input := data if len(input) == 0 { - return nil, stderrors.New("empty string is an invalid datetime format") + return nil, fmt.Errorf("empty string is an invalid datetime format: %w", ErrFormat) } return ParseDateTime(input) case "duration": diff --git a/time.go b/time.go index 2f83187..310b112 100644 --- a/time.go +++ b/time.go @@ -18,7 +18,6 @@ import ( "database/sql/driver" "encoding/binary" "encoding/json" - "errors" "fmt" "regexp" "strings" @@ -40,13 +39,19 @@ func init() { Default.Add("datetime", &dt, IsDateTime) } -// IsDateTime returns true when the string is a valid date-time +// IsDateTime returns true when the string is a valid date-time. +// +// JSON datetime format consist of a date and a time separated by a "T", e.g. 2012-04-23T18:25:43.511Z. func IsDateTime(str string) bool { - if len(str) < 4 { + const ( + minDateTimeLength = 4 + minParts = 2 + ) + if len(str) < minDateTimeLength { return false } s := strings.Split(strings.ToLower(str), "t") - if len(s) < 2 || !IsDate(s[0]) { + if len(s) < minParts || !IsDate(s[0]) { return false } @@ -91,7 +96,7 @@ var ( // MarshalFormat sets the time resolution format used for marshaling time (set to milliseconds) MarshalFormat = RFC3339Millis - // NormalizeTimeForMarshal provides a normalization function on time befeore marshalling (e.g. time.UTC). + // NormalizeTimeForMarshal provides a normalization function on time before marshalling (e.g. time.UTC). // By default, the time value is not changed. NormalizeTimeForMarshal = func(t time.Time) time.Time { return t } @@ -172,7 +177,7 @@ func (t *DateTime) Scan(raw interface{}) error { case nil: *t = DateTime{} default: - return fmt.Errorf("cannot sql.Scan() strfmt.DateTime from: %#v", v) + return fmt.Errorf("cannot sql.Scan() strfmt.DateTime from: %#v: %w", v, ErrFormat) } return nil @@ -226,20 +231,23 @@ func (t *DateTime) UnmarshalBSON(data []byte) error { return nil } +const bsonDateLength = 8 + // 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. - + // Time is undefined. That's why we use time.Nanosecond() instead. tNorm := NormalizeTimeForMarshal(time.Time(t)) - i64 := tNorm.Unix()*1000 + int64(tNorm.Nanosecond())/1e6 + i64 := tNorm.UnixMilli() - buf := make([]byte, 8) - binary.LittleEndian.PutUint64(buf, uint64(i64)) + buf := make([]byte, bsonDateLength) + // int64 -> uint64 conversion is safe here + binary.LittleEndian.PutUint64(buf, uint64(i64)) //nolint:gosec return bson.TypeDateTime, buf, nil } @@ -254,13 +262,14 @@ func (t *DateTime) UnmarshalBSONValue(tpe bsontype.Type, data []byte) error { return nil } - if len(data) != 8 { - return errors.New("bson date field length not exactly 8 bytes") + if len(data) != bsonDateLength { + return fmt.Errorf("bson date field length not exactly 8 bytes: %w", ErrFormat) } - i64 := int64(binary.LittleEndian.Uint64(data)) + // it's ok to get negative values after conversion + i64 := int64(binary.LittleEndian.Uint64(data)) //nolint:gosec // TODO: Use bsonprim.DateTime.Time() method - *t = DateTime(time.Unix(i64/1000, i64%1000*1000000)) + *t = DateTime(time.UnixMilli(i64)) return nil } diff --git a/ulid.go b/ulid.go index e71aff7..434eb01 100644 --- a/ulid.go +++ b/ulid.go @@ -4,7 +4,6 @@ import ( cryptorand "crypto/rand" "database/sql/driver" "encoding/json" - "errors" "fmt" "io" "sync" @@ -98,7 +97,7 @@ func NewULID() (ULID, error) { obj := ulidEntropyPool.Get() entropy, ok := obj.(io.Reader) if !ok { - return u, fmt.Errorf("failed to cast %+v to io.Reader", obj) + return u, fmt.Errorf("failed to cast %+v to io.Reader: %w", obj, ErrFormat) } id, err := ulid.New(ulid.Now(), entropy) @@ -181,12 +180,12 @@ func (u *ULID) UnmarshalBSON(data []byte) error { 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) + return fmt.Errorf("couldn't parse bson bytes as ULID: %w: %w", err, ErrFormat) } u.ULID = id return nil } - return errors.New("couldn't unmarshal bson bytes as ULID") + return fmt.Errorf("couldn't unmarshal bson bytes as ULID: %w", ErrFormat) } // DeepCopyInto copies the receiver and writes its value into out. diff --git a/ulid_test.go b/ulid_test.go index 02f4879..8be6714 100644 --- a/ulid_test.go +++ b/ulid_test.go @@ -201,9 +201,9 @@ func TestFormatULID_Scan(t *testing.T) { 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: %w", raw, ErrFormat) } - return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v", raw) + return u, fmt.Errorf("cannot sql.Scan() strfmt.ULID from: %#v: %w", raw, ErrFormat) } // get underlying binary implementation which is actually [16]byte