package dbx import ( "database/sql/driver" "fmt" "io" "github.com/segmentio/ksuid" ) // BinaryKSUID wraps segmentio/ksuid for binary-column storage (BINARY(20), BYTEA). // SQL Scan/Value use the raw 20-byte encoding. GraphQL transit uses a string // scalar (base62 KSUID). type BinaryKSUID struct { ksuid.KSUID } // NewBinaryKSUID generates a new BinaryKSUID. func NewBinaryKSUID() BinaryKSUID { return BinaryKSUID{KSUID: ksuid.New()} } // ParseBinaryKSUID parses a BinaryKSUID from its raw 20-byte form. func ParseBinaryKSUID(b []byte) (BinaryKSUID, error) { id, err := ksuid.FromBytes(b) if err != nil { return BinaryKSUID{}, err } return BinaryKSUID{KSUID: id}, nil } // AsStringKSUID returns a StringKSUID view of the same identifier. func (b BinaryKSUID) AsStringKSUID() StringKSUID { return StringKSUID{KSUID: b.KSUID} } // Scan implements sql.Scanner for binary-backed KSUID columns. func (b *BinaryKSUID) Scan(src any) error { switch v := src.(type) { case nil: *b = BinaryKSUID{} return nil case []byte: if len(v) == ksuidStringLength { return fmt.Errorf("BinaryKSUID.Scan: string-encoded KSUID (%d bytes); use StringKSUID", len(v)) } id, err := ksuid.FromBytes(v) if err != nil { return fmt.Errorf("BinaryKSUID.Scan: %w", err) } *b = BinaryKSUID{KSUID: id} return nil case string: return fmt.Errorf("BinaryKSUID.Scan: string value %q; use StringKSUID or store raw bytes", v) default: return fmt.Errorf("BinaryKSUID.Scan: unable to scan type %T", v) } } // Value implements driver.Valuer for binary-backed KSUID columns. func (b BinaryKSUID) Value() (driver.Value, error) { if b.IsNil() { return nil, nil } return b.Bytes(), nil } // UnmarshalGQL implements the graphql.Unmarshaler interface. func (b *BinaryKSUID) UnmarshalGQL(value any) error { return unmarshalKSUIDFromGQL(value, b.UnmarshalText) } // MarshalGQL implements the graphql.Marshaler interface. func (b BinaryKSUID) MarshalGQL(w io.Writer) { marshalKSUIDToGQL(w, b.KSUID) }