Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions common.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ func (t cborType) String() string {
}
}

// Type is the CBOR major type of an encoded data item, as defined in
// RFC 8949 Section 3.1. It is reported by RawMessage.Type.
type Type uint8

const (
TypePositiveInt Type = Type(cborTypePositiveInt) // unsigned integer (major type 0)
TypeNegativeInt Type = Type(cborTypeNegativeInt) // negative integer (major type 1)
TypeByteString Type = Type(cborTypeByteString) // byte string (major type 2)
TypeTextString Type = Type(cborTypeTextString) // UTF-8 text string (major type 3)
TypeArray Type = Type(cborTypeArray) // array (major type 4)
TypeMap Type = Type(cborTypeMap) // map (major type 5)
TypeTag Type = Type(cborTypeTag) // tagged data item (major type 6)
TypePrimitives Type = Type(cborTypePrimitives) // simple values and floating-point numbers (major type 7)
)

// String returns a human-readable description of the CBOR major type,
// such as "array" or "UTF-8 text string".
func (t Type) String() string {
return cborType(t).String()
}

type additionalInformation uint8

const (
Expand Down
25 changes: 25 additions & 0 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,3 +569,28 @@ func Example_webAuthn() {
}
fmt.Printf("%+v", v)
}

func ExampleRawMessage_Type() {
// A CBOR stream can hold data items of different major types. RawMessage.Type
// reports the outer kind of a raw value so it can be routed without a full decode.
encoded := []string{
"83010203", // [1, 2, 3]
"6568656c6c6f", // "hello"
"a1636b657963766c61", // {"key": "vla"}
"f6", // null
}
for _, s := range encoded {
data, _ := hex.DecodeString(s)
t, err := cbor.RawMessage(data).Type()
if err != nil {
fmt.Println("error:", err)
continue
}
fmt.Println(t)
}
// Output:
// array
// UTF-8 text string
// map
// primitives
}
15 changes: 15 additions & 0 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,3 +376,18 @@ func (m *RawMessage) UnmarshalCBOR(data []byte) error {
*m = append((*m)[0:0], data...)
return nil
}

// Type returns the CBOR major type of the encoded data item in m, as defined
// in RFC 8949 Section 3.1. It provides a cheap way to branch on the outer kind
// of a raw value without fully decoding it, similar to inspecting the first byte
// of a json.RawMessage.
//
// Only the initial byte is examined; Type does not verify that m holds a
// well-formed CBOR data item. Use Wellformed if you need that guarantee.
// Type returns an error if m is empty.
func (m RawMessage) Type() (Type, error) {
if len(m) == 0 {
return 0, errors.New("cbor.RawMessage: Type called on empty message")
}
return Type(getType(m[0])), nil
}
54 changes: 54 additions & 0 deletions stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1720,3 +1720,57 @@ func (r *recoverableReader) Read(b []byte) (int, error) {
}
return r.nBytesReader.Read(b)
}

func TestRawMessageType(t *testing.T) {
testCases := []struct {
name string
m RawMessage
want Type
wantStr string
}{
{"positive integer", RawMessage(mustHexDecode("00")), TypePositiveInt, "positive integer"},
{"positive integer with argument", RawMessage(mustHexDecode("1903e8")), TypePositiveInt, "positive integer"},
{"negative integer", RawMessage(mustHexDecode("20")), TypeNegativeInt, "negative integer"},
{"byte string", RawMessage(mustHexDecode("43010203")), TypeByteString, "byte string"},
{"indefinite-length byte string", RawMessage(mustHexDecode("5f42010243030405ff")), TypeByteString, "byte string"},
{"text string", RawMessage(mustHexDecode("63616263")), TypeTextString, "UTF-8 text string"},
{"array", RawMessage(mustHexDecode("83010203")), TypeArray, "array"},
{"indefinite-length array", RawMessage(mustHexDecode("9f010203ff")), TypeArray, "array"},
{"map", RawMessage(mustHexDecode("a201020304")), TypeMap, "map"},
{"tag", RawMessage(mustHexDecode("c074323031332d30332d32315432303a30343a30305a")), TypeTag, "tag"},
{"boolean primitive", RawMessage(mustHexDecode("f5")), TypePrimitives, "primitives"},
{"null primitive", RawMessage(mustHexDecode("f6")), TypePrimitives, "primitives"},
{"float primitive", RawMessage(mustHexDecode("fb3ff199999999999a")), TypePrimitives, "primitives"},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got, err := tc.m.Type()
if err != nil {
t.Fatalf("RawMessage(0x%x).Type() returned error %v", []byte(tc.m), err)
}
if got != tc.want {
t.Errorf("RawMessage(0x%x).Type() = %d, want %d", []byte(tc.m), got, tc.want)
}
if got.String() != tc.wantStr {
t.Errorf("RawMessage(0x%x).Type().String() = %q, want %q", []byte(tc.m), got.String(), tc.wantStr)
}
})
}
}

func TestRawMessageTypeEmpty(t *testing.T) {
for _, m := range []RawMessage{nil, {}} {
got, err := m.Type()
if err == nil {
t.Errorf("RawMessage(%v).Type() = %d with no error, want error", []byte(m), got)
}
}
}

func TestTypeStringInvalid(t *testing.T) {
got := Type(0x01).String()
want := "Invalid type 1"
if got != want {
t.Errorf("Type(0x01).String() = %q, want %q", got, want)
}
}