diff --git a/README.md b/README.md index 27a0953..209fd1d 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ A `Union` holds one ID of any of several types and picks the type by prefix: ```go type principalUnion struct{} -func (principalUnion) Members() []trogonobjectid.MemberType { +func (principalUnion) Types() []trogonobjectid.MemberType { return []trogonobjectid.MemberType{ trogonobjectid.MemberOf[userType](), trogonobjectid.MemberOf[systemType](), @@ -61,9 +61,8 @@ func (principalUnion) Members() []trogonobjectid.MemberType { type PrincipalID = trogonobjectid.Union[principalUnion] p, err := trogonobjectid.ParseUnion[principalUnion]("system_cron") -switch m := p.Member().(type) { -case SystemID: - // m.Bare() == "cron" +if sys, ok := trogonobjectid.UnionAs[systemType](p); ok { + // sys.Bare() == "cron" } ``` diff --git a/union.go b/union.go index 0c071db..e85d516 100644 --- a/union.go +++ b/union.go @@ -43,7 +43,7 @@ func MemberOf[T Definer]() MemberType { // // type principalUnion struct{} // -// func (principalUnion) Members() []trogonobjectid.MemberType { +// func (principalUnion) Types() []trogonobjectid.MemberType { // return []trogonobjectid.MemberType{ // trogonobjectid.MemberOf[userType](), // trogonobjectid.MemberOf[systemType](), @@ -52,11 +52,11 @@ func MemberOf[T Definer]() MemberType { // // type PrincipalID = trogonobjectid.Union[principalUnion] type UnionDefiner interface { - Members() []MemberType + Types() []MemberType } // Union holds one ID of any of its member types, chosen by prefix when parsing. -// Use a type switch on Member to get the typed ID back. +// Use UnionAs, or a type switch on Member, to get the typed ID back. // // Every encoding uses the member's full form, because the prefix is the only thing // that tells the member types apart. The zero value is an absent ID. @@ -77,7 +77,7 @@ func unionSpecFor[U UnionDefiner]() (*unionSpec, error) { func newUnionSpec[U UnionDefiner]() (unionSpec, error) { var marker U - members := marker.Members() + members := marker.Types() if len(members) == 0 { return unionSpec{}, fmt.Errorf("%w: %T has no members", ErrInvalidDefinition, marker) } @@ -169,6 +169,13 @@ func MustParseUnion[U UnionDefiner](str string) Union[U] { // Member returns the wrapped ID, or nil for the zero union. func (u Union[U]) Member() Member { return u.member } +// UnionAs returns u's member as an ID[T], and false when u holds another type or +// is zero. It narrows a union without a type switch. +func UnionAs[T Definer, U UnionDefiner](u Union[U]) (ID[T], bool) { + id, ok := u.member.(ID[T]) + return id, ok +} + // IsZero reports whether u is the zero (absent) union. func (u Union[U]) IsZero() bool { return u.member == nil } diff --git a/union_test.go b/union_test.go index 4d1b88c..08dfd68 100644 --- a/union_test.go +++ b/union_test.go @@ -24,7 +24,7 @@ type SystemID = trogonobjectid.ID[systemType] type principalUnion struct{} -func (principalUnion) Members() []trogonobjectid.MemberType { +func (principalUnion) Types() []trogonobjectid.MemberType { return []trogonobjectid.MemberType{ trogonobjectid.MemberOf[userType](), trogonobjectid.MemberOf[systemType](), @@ -48,7 +48,7 @@ func (acmeAdminType) Definition() trogonobjectid.Definition { type overlapUnion struct{} -func (overlapUnion) Members() []trogonobjectid.MemberType { +func (overlapUnion) Types() []trogonobjectid.MemberType { return []trogonobjectid.MemberType{ trogonobjectid.MemberOf[acmeType](), trogonobjectid.MemberOf[acmeAdminType](), @@ -57,11 +57,11 @@ func (overlapUnion) Members() []trogonobjectid.MemberType { type emptyUnion struct{} -func (emptyUnion) Members() []trogonobjectid.MemberType { return nil } +func (emptyUnion) Types() []trogonobjectid.MemberType { return nil } type duplicateUnion struct{} -func (duplicateUnion) Members() []trogonobjectid.MemberType { +func (duplicateUnion) Types() []trogonobjectid.MemberType { return []trogonobjectid.MemberType{ trogonobjectid.MemberOf[userType](), trogonobjectid.MemberOf[userType](), @@ -70,7 +70,7 @@ func (duplicateUnion) Members() []trogonobjectid.MemberType { type badMemberUnion struct{} -func (badMemberUnion) Members() []trogonobjectid.MemberType { +func (badMemberUnion) Types() []trogonobjectid.MemberType { return []trogonobjectid.MemberType{trogonobjectid.MemberOf[badType]()} } @@ -249,6 +249,21 @@ func TestUnionZero(t *testing.T) { assert.Empty(t, p.String()) } +func TestUnionAs(t *testing.T) { + user := trogonobjectid.MustNew[userType]("abc-123") + u := trogonobjectid.MustNewUnion[principalUnion](user) + + got, ok := trogonobjectid.UnionAs[userType](u) + require.True(t, ok) + assert.Equal(t, user, got) + + _, ok = trogonobjectid.UnionAs[systemType](u) + assert.False(t, ok) + + _, ok = trogonobjectid.UnionAs[userType](trogonobjectid.Union[principalUnion]{}) + assert.False(t, ok) +} + func TestUnionSpanner(t *testing.T) { want := trogonobjectid.MustNewUnion[principalUnion](trogonobjectid.MustNew[userType]("abc-123"))