Files
cof-spec/ident/ident.go
Yonah 87f59d0cf9 ident: implement full representation
This can be used with both system and member identifiers.

Signed-off-by: Yonah <contrib@gensokyo.uk>
2026-03-22 17:11:09 +09:00

100 lines
2.6 KiB
Go

// 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:])
}