Skip to content

Commit

Permalink
Validate the tag-id byte array as UUID.
Browse files Browse the repository at this point in the history
Validate the tag-id byte array as UUID.  Besides, given that the type of
the byte array is well defined and it has a known string representation,
allow it in XML and JSON serialisations.

Also, increase test coverage (partially addressing #12)

Fixes #18
  • Loading branch information
thomas-fossati committed Sep 6, 2021
1 parent 6491ae6 commit 8db66de
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-go-cover.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# 1. Change workflow name from "cover 100%" to "cover ≥92.5%". Script will automatically use 92.5%.
# 2. Update README.md to use the new path to badge.svg because the path includes the workflow name.

name: cover ≥76%
name: cover ≥78%
on: [push, pull_request]
jobs:

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go 1.15

require (
github.com/fxamacker/cbor/v2 v2.3.0
github.com/google/uuid v1.3.0
github.com/stretchr/testify v1.6.1
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fxamacker/cbor/v2 v2.3.0 h1:aM45YGMctNakddNNAezPxDUpv38j44Abh+hifNuqXik=
github.com/fxamacker/cbor/v2 v2.3.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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=
Expand Down
143 changes: 88 additions & 55 deletions tagid.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,105 +4,125 @@
package swid

import (
"encoding/hex"
"encoding/json"
"encoding/xml"
"errors"
"fmt"

"github.com/fxamacker/cbor/v2"
"github.com/google/uuid"
)

// TagID is the type of a tag identifier. Allowed formats (enforced via
// checkTagID) are string or [16]byte
// TagID is the type of a tag identifier. Allowed formats are string or
// a valid universally unique identifier (UUID) as defined by RFC4122.
type TagID struct {
val interface{}
}

// NewTagID returns a TagID initialized with the supplied value v
// v is either a string or a [16]byte
func NewTagID(v interface{}) *TagID {
if checkTagID(v) != nil {
switch t := v.(type) {
case string:
tagID, _ := string2TagID(t)
return tagID
case []byte:
tagID, _ := NewTagIDFromUUIDBytes(t)
return tagID
default:
return nil
}
return &TagID{v}
}

// String returns the value of the TagID as string. If the TagID has type
// [16]byte the Base 16 encoding is returned
func (t TagID) String() string {
switch v := t.val.(type) {
case string:
return v
case []byte:
return hex.EncodeToString(v)
default:
return "unknown type for tag-id"
func string2TagID(s string) (*TagID, error) {
if tagID, err := NewTagIDFromUUIDString(s); err == nil {
return tagID, nil
}

if tagID, err := NewTagIDFromString(s); err == nil {
return tagID, nil
}

return nil, errors.New("tag-id is neither a UUID nor a valid string")
}

func checkTagID(v interface{}) error {
switch t := v.(type) {
case string:
case []byte:
if len(t) != 16 {
return errors.New("binary tag-id MUST be 16 bytes")
}
default:
return fmt.Errorf("tag-id MUST be []byte or string; got %T", v)
func NewTagIDFromString(s string) (*TagID, error) {
if s == "" {
return nil, errors.New("empty string")
}
return &TagID{s}, nil
}

return nil
func NewTagIDFromUUIDString(s string) (*TagID, error) {
u, err := uuid.Parse(s)
if err != nil {
return nil, err
}

return &TagID{u}, nil
}

func NewTagIDFromUUIDBytes(b []byte) (*TagID, error) {
u, err := uuid.FromBytes(b)
if err != nil {
return nil, err
}

return &TagID{u}, nil
}

func (t TagID) isString() bool {
switch t.val.(type) {
// String returns the value of the TagID as string. If the TagID has type UUID,
// the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, is returned
func (t TagID) String() string {
switch v := t.val.(type) {
case string:
return true
return v
case uuid.UUID:
return v.String()
default:
return "unknown type for tag-id"
}
return false
}

// MarshalXMLAttr encodes the TagID receiver as XML attribute
func (t TagID) MarshalXMLAttr(name xml.Name) (xml.Attr, error) {
if !t.isString() {
return xml.Attr{}, errors.New("only tag-id of type string can be serialized to XML")
}
return xml.Attr{Name: name, Value: t.String()}, nil
}

// UnmarshalXMLAttr decodes the supplied XML attribute into a TagID
// Note that this can only unmarshal to string.
func (t *TagID) UnmarshalXMLAttr(attr xml.Attr) error {
t.val = attr.Value
tagID, err := string2TagID(attr.Value)
if err != nil {
return fmt.Errorf("error unmarshaling tag-id %q: %w", attr.Value, err)
}

*t = *tagID

return nil
}

// MarshalJSON encodes the TagID receiver as JSON string
func (t TagID) MarshalJSON() ([]byte, error) {
if !t.isString() {
return nil, errors.New("only tag-id of type string can be serialized to JSON")
}

return json.Marshal(t.val)
return json.Marshal(t.String())
}

// UnmarshalJSON decodes the supplied JSON data into a TagID
// Note that this can only unmarshal to string.
// UnmarshalJSON decodes the supplied JSON data into a TagID. If TagID is of
// type UUID, the string form, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, is
// expected.
func (t *TagID) UnmarshalJSON(data []byte) error {
var v interface{}
var s string

if err := json.Unmarshal(data, &v); err != nil {
return err
if err := json.Unmarshal(data, &s); err != nil {
return fmt.Errorf("error unmarshaling tag-id: %w", err)
}

switch s := v.(type) {
case string:
t.val = s
return nil
default:
return fmt.Errorf("expecting string, found %T instead", s)
tagID, err := string2TagID(s)
if err != nil {
return fmt.Errorf("error unmarshaling tag-id %q: %w", s, err)
}

*t = *tagID

return nil
}

// MarshalCBOR encodes the TagID receiver to CBOR
Expand All @@ -112,17 +132,30 @@ func (t TagID) MarshalCBOR() ([]byte, error) {

// UnmarshalCBOR decodes the supplied data into a TagID
func (t *TagID) UnmarshalCBOR(data []byte) error {
var v interface{}
var (
v interface{}
err error
tagID *TagID
)

if err := cbor.Unmarshal(data, &v); err != nil {
return err
}

if err := checkTagID(v); err != nil {
return err
switch typ := v.(type) {
case string:
tagID, err = NewTagIDFromString(typ)
case []byte:
tagID, err = NewTagIDFromUUIDBytes(typ)
default:
tagID, err = nil, fmt.Errorf("tag-id MUST be []byte or string; got %T", typ)
}

if err != nil {
return fmt.Errorf("error unmarshaling tag-id: %w", err)
}

t.val = v
*t = *tagID

return nil
}
Loading

0 comments on commit 8db66de

Please sign in to comment.