diff --git a/common.go b/common.go index 0387db60..ec038a49 100644 --- a/common.go +++ b/common.go @@ -139,34 +139,33 @@ func validBuiltinTag(tagNum uint64, contentHead byte) error { case tagNumRFC3339Time: // Tag content (date/time text string in RFC 3339 format) must be string type. if t != cborTypeTextString { - return fmt.Errorf( - "cbor: tag number %d must be followed by text string, got %s", + return newInadmissibleTagContentTypeError( tagNumRFC3339Time, - t.String(), - ) + "text string", + t.String()) } return nil case tagNumEpochTime: // Tag content (epoch date/time) must be uint, int, or float type. if t != cborTypePositiveInt && t != cborTypeNegativeInt && (contentHead < 0xf9 || contentHead > 0xfb) { - return fmt.Errorf( - "cbor: tag number %d must be followed by integer or floating-point number, got %s", + return newInadmissibleTagContentTypeError( tagNumEpochTime, - t.String(), - ) + "integer or floating-point number", + t.String()) } return nil case tagNumUnsignedBignum, tagNumNegativeBignum: // Tag content (bignum) must be byte type. if t != cborTypeByteString { - return fmt.Errorf( - "cbor: tag number %d or %d must be followed by byte string, got %s", - tagNumUnsignedBignum, - tagNumNegativeBignum, - t.String(), - ) + return newInadmissibleTagContentTypeErrorf( + fmt.Sprintf( + "tag number %d or %d must be followed by byte string, got %s", + tagNumUnsignedBignum, + tagNumNegativeBignum, + t.String(), + )) } return nil diff --git a/decode.go b/decode.go index 133b63fe..85842ac7 100644 --- a/decode.go +++ b/decode.go @@ -254,6 +254,45 @@ func (e *ByteStringExpectedFormatError) Unwrap() error { return e.err } +// InadmissibleTagContentTypeError is returned when unmarshaling built-in CBOR tags +// fails because of inadmissible type for tag content. Currently, the built-in +// CBOR tags in this codec are tags 0-3 and 21-23. +// See "Tag validity" in RFC 8949 Section 5.3.2. +type InadmissibleTagContentTypeError struct { + s string + tagNum int + expectedTagContentType string + gotTagContentType string +} + +func newInadmissibleTagContentTypeError( + tagNum int, + expectedTagContentType string, + gotTagContentType string, +) *InadmissibleTagContentTypeError { + return &InadmissibleTagContentTypeError{ + tagNum: tagNum, + expectedTagContentType: expectedTagContentType, + gotTagContentType: gotTagContentType, + } +} + +func newInadmissibleTagContentTypeErrorf(s string) *InadmissibleTagContentTypeError { + return &InadmissibleTagContentTypeError{s: "cbor: " + s} //nolint:goconst // ignore "cbor" +} + +func (e *InadmissibleTagContentTypeError) Error() string { + if e.s == "" { + return fmt.Sprintf( + "cbor: tag number %d must be followed by %s, got %s", + e.tagNum, + e.expectedTagContentType, + e.gotTagContentType, + ) + } + return e.s +} + // DupMapKeyMode specifies how to enforce duplicate map key. Two map keys are considered duplicates if: // 1. When decoding into a struct, both keys match the same struct field. The keys are also // considered duplicates if neither matches any field and decoding to interface{} would produce diff --git a/diagnose.go b/diagnose.go index 2794df3f..44afb866 100644 --- a/diagnose.go +++ b/diagnose.go @@ -401,11 +401,10 @@ func (di *diagnose) item() error { //nolint:gocyclo switch tagNum { case tagNumUnsignedBignum: if nt := di.d.nextCBORType(); nt != cborTypeByteString { - return fmt.Errorf( - "cbor: tag number %d must be followed by byte string, got %s", + return newInadmissibleTagContentTypeError( tagNumUnsignedBignum, - nt.String(), - ) + "byte string", + nt.String()) } b, _ := di.d.parseByteString() @@ -415,9 +414,9 @@ func (di *diagnose) item() error { //nolint:gocyclo case tagNumNegativeBignum: if nt := di.d.nextCBORType(); nt != cborTypeByteString { - return fmt.Errorf( - "cbor: tag number %d must be followed by byte string, got %s", + return newInadmissibleTagContentTypeError( tagNumNegativeBignum, + "byte string", nt.String(), ) }