feat: add StringKSUID and BinaryKSUID types
Replace the UUID wrapper with storage-specific KSUID types that share GraphQL string transit but use distinct SQL Scan/Value encodings. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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 the shared
|
||||
// string JSON transport (see ksuid_gql.go).
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user