diff --git a/uuid/README.md b/uuid/README.md new file mode 100644 index 0000000..3e30c3d --- /dev/null +++ b/uuid/README.md @@ -0,0 +1,46 @@ +# UUID Package + +This package provides utilities for UUID (Universally Unique Identifier) generation and manipulation using only Go's standard library. + +## Features +- Generate new UUIDs (v4) +- Parse UUID from string +- Convert UUID to string +- Validate a UUID string +- Compare two UUIDs +- Marshal/Unmarshal UUID to/from JSON +- Generate nil (zero value) UUID + +## Usage Example +```go +import "yourmodule/uuid" + +// Generate a new UUID (v4) +id := uuid.NewUUID() + +// Convert to string +s := id.String() + +// Validate a UUID string +valid := uuid.Validate(s) + +// Parse from string +parsed, err := uuid.Parse(s) + +// Compare UUIDs +isEqual := id.Equal(parsed) + +// Nil UUID +zero := uuid.Nil() + +// Marshal/Unmarshal JSON +b, _ := json.Marshal(id) +var u2 uuid.UUID +_ = json.Unmarshal(b, &u2) +``` + +## Testing +Run unit tests: +```sh +go test ./uuid +``` diff --git a/uuid/uuid.go b/uuid/uuid.go new file mode 100644 index 0000000..dc47d94 --- /dev/null +++ b/uuid/uuid.go @@ -0,0 +1,111 @@ +// Package uuid provides utilities for UUID generation and manipulation using Go's standard library only. +// +// Features: +// - Generate new UUIDs (v4) +// - Parse UUID from string +// - Convert UUID to string +// - Validate a UUID string +// - Compare two UUIDs +// - Marshal/Unmarshal UUID to/from JSON +// - Generate nil (zero value) UUID +// +// Usage Example: +// +// import "yourmodule/uuid" +// id := uuid.NewUUID() +// valid := uuid.Validate(id.String()) +// ... +package uuid + +import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "strings" +) + +// UUID represents a 128-bit universally unique identifier (UUID). +type UUID [16]byte + +// NewUUID generates a new random (version 4) UUID. +func NewUUID() UUID { + var u UUID + _, err := rand.Read(u[:]) + if err != nil { + panic("uuid: cannot generate random UUID: " + err.Error()) + } + // Set version (4) and variant bits as per RFC 4122 + u[6] = (u[6] & 0x0f) | 0x40 // Version 4 + u[8] = (u[8] & 0x3f) | 0x80 // Variant is 10 + return u +} + +// Parse parses a UUID from string (accepts canonical form only). +func Parse(s string) (UUID, error) { + var u UUID + s = strings.ToLower(s) + if len(s) != 36 { + return Nil(), errors.New("uuid: invalid length") + } + // Remove dashes + hexStr := strings.ReplaceAll(s, "-", "") + if len(hexStr) != 32 { + return Nil(), errors.New("uuid: invalid format") + } + b, err := hex.DecodeString(hexStr) + if err != nil || len(b) != 16 { + return Nil(), errors.New("uuid: invalid hex") + } + copy(u[:], b) + return u, nil +} + +// String returns the canonical string representation of the UUID. +func (u UUID) String() string { + b := u[:] + return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", + b[0:4], b[4:6], b[6:8], b[8:10], b[10:16], + ) +} + +// Validate checks if a string is a valid UUID. +func Validate(s string) bool { + _, err := Parse(s) + return err == nil +} + +// Equal compares two UUIDs for equality. +func (u UUID) Equal(other UUID) bool { + return u == other +} + +// Nil returns a nil (zero value) UUID. +func Nil() UUID { + return UUID{} +} + +// MarshalJSON implements the json.Marshaler interface. +func (u UUID) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (u *UUID) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + parsed, err := Parse(s) + if err != nil { + return err + } + *u = parsed + return nil +} + +// Format implements fmt.Formatter for UUID. +func (u UUID) Format(f fmt.State, c rune) { + fmt.Fprintf(f, "%s", u.String()) +} diff --git a/uuid/uuid_test.go b/uuid/uuid_test.go new file mode 100644 index 0000000..6e7a632 --- /dev/null +++ b/uuid/uuid_test.go @@ -0,0 +1,69 @@ +package uuid + +import ( + "encoding/json" + "testing" +) + +func TestNewUUID(t *testing.T) { + u := NewUUID() + if u.String() == "" || !Validate(u.String()) { + t.Errorf("NewUUID generated invalid UUID: %s", u.String()) + } +} + +func TestParse(t *testing.T) { + u1 := NewUUID() + u2, err := Parse(u1.String()) + if err != nil { + t.Fatalf("Parse() error: %v", err) + } + if !u1.Equal(u2) { + t.Errorf("Parsed UUID does not match original") + } +} + +func TestValidate(t *testing.T) { + u := NewUUID().String() + if !Validate(u) { + t.Errorf("Validate() failed for valid UUID: %s", u) + } + if Validate("not-a-uuid") { + t.Errorf("Validate() passed for invalid UUID") + } +} + +func TestEqual(t *testing.T) { + u1 := NewUUID() + u2, _ := Parse(u1.String()) + if !u1.Equal(u2) { + t.Errorf("Equal() failed for identical UUIDs") + } + u3 := NewUUID() + if u1.Equal(u3) { + t.Errorf("Equal() passed for different UUIDs") + } +} + +func TestNil(t *testing.T) { + nilUUID := Nil() + if nilUUID.String() != "00000000-0000-0000-0000-000000000000" { + t.Errorf("Nil() did not return zero UUID") + } +} + +func TestMarshalUnmarshalJSON(t *testing.T) { + u := NewUUID() + data, err := json.Marshal(u) + if err != nil { + t.Fatalf("MarshalJSON() error: %v", err) + } + + var u2 UUID + if err := json.Unmarshal(data, &u2); err != nil { + t.Fatalf("UnmarshalJSON() error: %v", err) + } + if !u.Equal(u2) { + t.Errorf("UnmarshalJSON() did not produce equal UUID") + } +}