package domain import ( "crypto/rand" "encoding/hex" "strings" ) type ObjectID [16]byte type BlobID [32]byte func NewObjectID() (ObjectID, error) { var id ObjectID _, err := rand.Read(id[:]) return id, err } func ParseObjectID(s string) (ObjectID, error) { s = strings.TrimSpace(s) b, err := hex.DecodeString(s) if err != nil || len(b) != len(ObjectID{}) { return ObjectID{}, ErrInvalidObjectID } var id ObjectID copy(id[:], b) return id, nil } func (id ObjectID) String() string { return hex.EncodeToString(id[:]) } func ParseBlobID(s string) (BlobID, error) { s = strings.TrimSpace(s) b, err := hex.DecodeString(s) if err != nil || len(b) != len(BlobID{}) { return BlobID{}, ErrInvalidBlobID } var id BlobID copy(id[:], b) return id, nil } func (id BlobID) String() string { return hex.EncodeToString(id[:]) } type ObjectState struct { ID ObjectID Name string Tags []string Blob BlobID HasBlob bool Deleted bool } // Keep normalization here so all layers agree. func NormalizeName(s string) string { s = strings.TrimSpace(s) return s } func NormalizeTags(tags []string) []string { out := make([]string, 0, len(tags)) seen := make(map[string]struct{}, len(tags)) for _, t := range tags { t = strings.TrimSpace(strings.ToLower(t)) if t == "" { continue } if _, ok := seen[t]; ok { continue } seen[t] = struct{}{} out = append(out, t) } return out }