zoobzio January 5, 2025 Edit this page

Custom Types

Working with named types, enums, and byte arrays.

Named Types

Named types (type aliases with semantic meaning) are fully supported.

String-based Types

type UserID string
type Email string
type Currency string

type User struct {
    ID       UserID
    Email    Email
    Currency Currency
}

atomizer, _ := atom.Use[User]()
user := &User{
    ID:       "usr_123",
    Email:    "alice@example.com",
    Currency: "USD",
}

atom := atomizer.Atomize(user)
// All stored in Strings table
// atom.Strings["ID"] = "usr_123"
// atom.Strings["Email"] = "alice@example.com"
// atom.Strings["Currency"] = "USD"

Integer-based Types (Enums)

type Status int
type Priority int

const (
    StatusPending Status = iota
    StatusActive
    StatusComplete
)

const (
    PriorityLow Priority = iota
    PriorityMedium
    PriorityHigh
)

type Task struct {
    Name     string
    Status   Status
    Priority Priority
}

atomizer, _ := atom.Use[Task]()
task := &Task{
    Name:     "Review PR",
    Status:   StatusActive,
    Priority: PriorityHigh,
}

atom := atomizer.Atomize(task)
// atom.Strings["Name"] = "Review PR"
// atom.Ints["Status"] = 1
// atom.Ints["Priority"] = 2

Float-based Types

type Score float64
type Percentage float64

type Result struct {
    Score      Score
    Confidence Percentage
}

atomizer, _ := atom.Use[Result]()
result := &Result{Score: 95.5, Confidence: 0.87}

atom := atomizer.Atomize(result)
// atom.Floats["Score"] = 95.5
// atom.Floats["Confidence"] = 0.87

Named Slices

Slice of Named Types

type Tag string
type UserID string

type User struct {
    ID   UserID
    Tags []Tag
}

atomizer, _ := atom.Use[User]()
user := &User{
    ID:   "usr_123",
    Tags: []Tag{"admin", "verified", "premium"},
}

atom := atomizer.Atomize(user)
// atom.Strings["ID"] = "usr_123"
// atom.StringSlices["Tags"] = ["admin", "verified", "premium"]

Round-trip Preservation

Named types are preserved through atomization:

restored, _ := atomizer.Deatomize(atom)
fmt.Printf("%T\n", restored.ID)   // main.UserID
fmt.Printf("%T\n", restored.Tags) // []main.Tag

Byte Slice Types

Named byte Types

Common examples: net.IP, json.RawMessage, custom binary types.

type IP []byte
type Hash []byte
type RawJSON []byte

type Record struct {
    ClientIP IP
    Checksum Hash
    Payload  RawJSON
}

atomizer, _ := atom.Use[Record]()
record := &Record{
    ClientIP: IP{192, 168, 1, 1},
    Checksum: Hash{0xde, 0xad, 0xbe, 0xef},
    Payload:  RawJSON(`{"key": "value"}`),
}

atom := atomizer.Atomize(record)
// All stored in Bytes table
// atom.Bytes["ClientIP"] = [192, 168, 1, 1]
// atom.Bytes["Checksum"] = [0xde, 0xad, 0xbe, 0xef]
// atom.Bytes["Payload"] = [123, 34, 107, ...]

net.IP Example

import "net"

type Connection struct {
    LocalAddr  net.IP
    RemoteAddr net.IP
    Port       int64
}

atomizer, _ := atom.Use[Connection]()
conn := &Connection{
    LocalAddr:  net.ParseIP("127.0.0.1"),
    RemoteAddr: net.ParseIP("10.0.0.1"),
    Port:       8080,
}

atom := atomizer.Atomize(conn)
// atom.Bytes["LocalAddr"] = [127, 0, 0, 1]
// atom.Bytes["RemoteAddr"] = [10, 0, 0, 1]
// atom.Ints["Port"] = 8080

Fixed-Size Byte Arrays

Arrays of bytes [N]byte are supported and stored in the Bytes table.

Basic Usage

type HashID [32]byte
type UUID [16]byte

type Document struct {
    ID   UUID
    Hash HashID
}

atomizer, _ := atom.Use[Document]()

var id UUID
copy(id[:], "0123456789abcdef")

var hash HashID
copy(hash[:], "abcdefghijklmnopqrstuvwxyz012345")

doc := &Document{ID: id, Hash: hash}
atom := atomizer.Atomize(doc)
// atom.Bytes["ID"] = [48, 49, 50, 51, ...] (16 bytes)
// atom.Bytes["Hash"] = [97, 98, 99, 100, ...] (32 bytes)

Size Validation

During deatomization, byte slice length must match array size:

type Fixed struct {
    Data [4]byte
}

atomizer, _ := atom.Use[Fixed]()

// Correct size
atom := &atom.Atom{Bytes: map[string][]byte{"Data": {1, 2, 3, 4}}}
fixed, err := atomizer.Deatomize(atom) // OK

// Wrong size
atom = &atom.Atom{Bytes: map[string][]byte{"Data": {1, 2}}}
_, err = atomizer.Deatomize(atom)
// err: "field Data: expected 4 bytes, got 2"

Pointer to Named Types

type UserID string
type Score float64

type OptionalUser struct {
    ID    *UserID
    Score *Score
}

atomizer, _ := atom.Use[OptionalUser]()

id := UserID("usr_123")
score := Score(95.5)

user := &OptionalUser{ID: &id, Score: &score}
atom := atomizer.Atomize(user)
// atom.StringPtrs["ID"] = &"usr_123"
// atom.FloatPtrs["Score"] = &95.5

// With nil values
user = &OptionalUser{ID: nil, Score: nil}
atom = atomizer.Atomize(user)
// atom.StringPtrs["ID"] = nil
// atom.FloatPtrs["Score"] = nil

Type Mapping Reference

Named Type BaseStorage Table
type X stringstrings
type X int*ints
type X uint*uints
type X float*floats
type X boolbools
type X []bytebytes
type X [N]bytebytes
type X []string (etc)corresponding slice table
type X *string (etc)corresponding pointer table

Best Practices

Use Named Types for Domain Concepts

// Good - clear semantics
type UserID string
type Email string
type Money int64 // cents

type User struct {
    ID      UserID
    Email   Email
    Balance Money
}

// Bad - primitive soup
type User struct {
    ID      string
    Email   string
    Balance int64
}

Use Enums Instead of Strings

// Good - type-safe
type Status int
const (
    StatusActive Status = iota
    StatusInactive
)

// Bad - error-prone
type User struct {
    Status string // "active", "inactive", typos possible
}

Next Steps