// Package ident is the reference implementation of system and member // identifiers. package ident import ( "encoding" "errors" "fmt" "strconv" "unsafe" ) var ( // ErrNewline is returned for identifiers found to contain newline characters. ErrNewline = errors.New("identifier contains newline characters") // ErrSeparator is returned for representations of [F] missing its separator // byte in its expected index. ErrSeparator = errors.New("identifier has incorrect separator byte") ) // UnexpectedSizeError describes a malformed string representation of an // identifier, with unexpected length. type UnexpectedSizeError struct { Data []byte Want int } func (e *UnexpectedSizeError) Error() string { return "got " + strconv.Itoa(len(e.Data)) + " bytes for " + "a " + strconv.Itoa(e.Want) + "-byte identifier" } // MS is a system or member identifier. type MS interface { encoding.TextMarshaler encoding.TextUnmarshaler fmt.Stringer // EncodedSize returns the number of bytes appended by Encode. EncodedSize() int // Encode appends the canonical string representation of MS to dst and // returns the extended buffer. Encode(dst []byte) []byte // ident is a no-op function but serves to distinguish types that are // identifiers from ordinary types with custom marshaler/unmarshaler // behaviour: a type is an identifier part if it has an ident method. ident() } // F represents a full system or member identifier. type F[V any, T interface { MS *V }] struct { // Underlying system or member. I T // Split from instance suffix after [Separator] byte. Remote Remote } // Separator is the byte separating representation of [MS] from [Remote]. const Separator = ':' // Encode appends the canonical string representation of full to dst and returns // the extended buffer. func (full *F[V, T]) Encode(dst []byte) []byte { dst = full.I.Encode(dst) dst = append(dst, Separator) dst = full.Remote.Encode(dst) return dst } // String returns the canonical string representation of full. func (full *F[V, T]) String() string { s := full.Encode(nil) return unsafe.String(unsafe.SliceData(s), len(s)) } // MarshalText returns the result of Encode. func (full *F[V, T]) MarshalText() (data []byte, err error) { return full.Encode(nil), nil } // UnmarshalText strictly decodes data into full. func (full *F[V, T]) UnmarshalText(data []byte) (err error) { sz := full.I.EncodedSize() if len(data) < sz+1 || data[sz] != Separator { return ErrSeparator } if full.I == nil { full.I = new(V) } if err = full.I.UnmarshalText(data[:sz]); err != nil { return } return full.Remote.UnmarshalText(data[sz+1:]) }