Documentation
¶
Overview ¶
Example (BasicComparison) ¶
package main
import (
"fmt"
"time"
"github.com/plaenen/eventstore/pkg/compare"
)
// Example types
type Account struct {
ID string
Email string
Name string
Balance int64
Profile Profile
CreatedAt time.Time
UpdatedAt time.Time
Metadata Metadata
}
type Profile struct {
Bio string
AvatarURL string
Location string
}
type Metadata struct {
Version int
Timestamp time.Time
Tags []string
}
func main() {
account1 := Account{
ID: "acc-1",
Email: "user@example.com",
Name: "John Doe",
}
account2 := Account{
ID: "acc-1",
Email: "user@example.com",
Name: "John Doe",
}
result := compare.CompareAggregates(account1, account2, nil)
fmt.Println(compare.FormatDiff(result))
}
Output: ✓ Aggregates are equal
Example (EventSourcingVerification) ¶
package main
import (
"fmt"
"time"
"github.com/plaenen/eventstore/pkg/compare"
)
// Example types
type Account struct {
ID string
Email string
Name string
Balance int64
Profile Profile
CreatedAt time.Time
UpdatedAt time.Time
Metadata Metadata
}
type Profile struct {
Bio string
AvatarURL string
Location string
}
type Metadata struct {
Version int
Timestamp time.Time
Tags []string
}
func main() {
// Simulate rebuilding an aggregate from events
original := Account{
ID: "acc-1",
Email: "user@example.com",
Name: "John Doe",
Balance: 10000,
}
// After rebuilding from events
rebuilt := Account{
ID: "acc-1",
Email: "user@example.com",
Name: "John Doe",
Balance: 10000,
}
// Compare, excluding runtime-generated fields
result := compare.CompareAggregates(original, rebuilt, []string{
"CreatedAt",
"UpdatedAt",
"Metadata.Timestamp",
})
if result.Equal {
fmt.Println("✓ Aggregate rebuild successful")
} else {
fmt.Printf("✗ Rebuild failed:\n%s\n", result.Diff)
}
}
Output: ✓ Aggregate rebuild successful
Example (ExcludeFields) ¶
package main
import (
"fmt"
"time"
"github.com/plaenen/eventstore/pkg/compare"
)
// Example types
type Account struct {
ID string
Email string
Name string
Balance int64
Profile Profile
CreatedAt time.Time
UpdatedAt time.Time
Metadata Metadata
}
type Profile struct {
Bio string
AvatarURL string
Location string
}
type Metadata struct {
Version int
Timestamp time.Time
Tags []string
}
func main() {
now := time.Now()
account1 := Account{
ID: "acc-1",
Email: "user@example.com",
CreatedAt: now,
UpdatedAt: now,
}
account2 := Account{
ID: "acc-2", // Different ID
Email: "user@example.com",
CreatedAt: now.Add(time.Hour), // Different timestamp
UpdatedAt: now.Add(time.Hour), // Different timestamp
}
// Compare, but ignore ID and timestamps
result := compare.CompareAggregates(account1, account2, []string{
"ID",
"CreatedAt",
"UpdatedAt",
})
fmt.Println(compare.FormatDiff(result))
}
Output: ✓ Aggregates are equal
Example (ExcludeNestedFields) ¶
package main
import (
"fmt"
"time"
"github.com/plaenen/eventstore/pkg/compare"
)
// Example types
type Account struct {
ID string
Email string
Name string
Balance int64
Profile Profile
CreatedAt time.Time
UpdatedAt time.Time
Metadata Metadata
}
type Profile struct {
Bio string
AvatarURL string
Location string
}
type Metadata struct {
Version int
Timestamp time.Time
Tags []string
}
func main() {
account1 := Account{
ID: "acc-1",
Email: "user@example.com",
Profile: Profile{
Bio: "Software Engineer",
AvatarURL: "https://example.com/avatar1.jpg",
Location: "San Francisco",
},
}
account2 := Account{
ID: "acc-1",
Email: "user@example.com",
Profile: Profile{
Bio: "Software Engineer",
AvatarURL: "https://example.com/avatar2.jpg", // Different
Location: "San Francisco",
},
}
// Exclude nested field
result := compare.CompareAggregates(account1, account2, []string{
"Profile.AvatarURL",
})
fmt.Println(compare.FormatDiff(result))
}
Output: ✓ Aggregates are equal
Example (MultipleOptions) ¶
package main
import (
"fmt"
"time"
"github.com/plaenen/eventstore/pkg/compare"
)
// Example types
type Account struct {
ID string
Email string
Name string
Balance int64
Profile Profile
CreatedAt time.Time
UpdatedAt time.Time
Metadata Metadata
}
type Profile struct {
Bio string
AvatarURL string
Location string
}
type Metadata struct {
Version int
Timestamp time.Time
Tags []string
}
func main() {
now := time.Now()
account1 := Account{
ID: "acc-1",
Email: "user@example.com",
Balance: 10000,
CreatedAt: now,
UpdatedAt: now,
}
account2 := Account{
ID: "acc-2", // Different
Email: "user@example.com",
Balance: 10000,
CreatedAt: now.Add(time.Hour), // Different
UpdatedAt: now.Add(time.Hour), // Different
}
// Use multiple options
result := compare.CompareAggregatesWithOptions(
account1, account2,
compare.IgnoreFields(Account{}, "ID", "CreatedAt", "UpdatedAt"),
compare.EquateEmpty(),
)
fmt.Println(compare.FormatDiff(result))
}
Output: ✓ Aggregates are equal
Example (WildcardExclusion) ¶
package main
import (
"fmt"
"time"
"github.com/plaenen/eventstore/pkg/compare"
)
// Example types
type Account struct {
ID string
Email string
Name string
Balance int64
Profile Profile
CreatedAt time.Time
UpdatedAt time.Time
Metadata Metadata
}
type Profile struct {
Bio string
AvatarURL string
Location string
}
type Metadata struct {
Version int
Timestamp time.Time
Tags []string
}
func main() {
now := time.Now()
account1 := Account{
ID: "acc-1",
Email: "user@example.com",
Metadata: Metadata{
Version: 1,
Timestamp: now,
Tags: []string{"active"},
},
}
account2 := Account{
ID: "acc-1",
Email: "user@example.com",
Metadata: Metadata{
Version: 2, // Different
Timestamp: now.Add(time.Hour), // Different
Tags: []string{"premium"}, // Different
},
}
// Exclude all metadata fields using wildcard
result := compare.CompareAggregates(account1, account2, []string{
"Metadata.*",
})
fmt.Println(compare.FormatDiff(result))
}
Output: ✓ Aggregates are equal
Example (WithCustomOptions) ¶
package main
import (
"fmt"
"time"
"github.com/plaenen/eventstore/pkg/compare"
)
// Example types
type Account struct {
ID string
Email string
Name string
Balance int64
Profile Profile
CreatedAt time.Time
UpdatedAt time.Time
Metadata Metadata
}
type Profile struct {
Bio string
AvatarURL string
Location string
}
type Metadata struct {
Version int
Timestamp time.Time
Tags []string
}
func main() {
account1 := Account{
ID: "acc-1",
Email: "user@example.com",
Metadata: Metadata{Tags: nil},
}
account2 := Account{
ID: "acc-1",
Email: "user@example.com",
Metadata: Metadata{Tags: []string{}}, // Empty slice vs nil
}
// Without EquateEmpty, these would be different
result := compare.CompareAggregatesWithOptions(
account1, account2,
compare.EquateEmpty(), // Treat nil == empty
)
fmt.Println(compare.FormatDiff(result))
}
Output: ✓ Aggregates are equal
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EquateEmpty ¶
EquateEmpty returns an option that considers nil and empty containers as equal.
Example:
result := CompareAggregatesWithOptions(
expected, actual,
EquateEmpty(), // nil slice == empty slice
)
func FormatDiff ¶
func FormatDiff(result *ComparisonResult) string
FormatDiff returns a formatted comparison result
func IgnoreFields ¶
IgnoreFields returns an option that ignores specific fields in a struct type.
Example:
result := CompareAggregatesWithOptions(
expected, actual,
IgnoreFields(User{}, "ID", "CreatedAt", "UpdatedAt"),
)
func IgnoreTypes ¶
IgnoreTypes ignores all values of the specified types during comparison.
Example:
result := CompareAggregatesWithOptions(
expected, actual,
IgnoreTypes(time.Time{}), // Ignore all time.Time fields
)
func IgnoreUnexportedFields ¶
IgnoreUnexportedFields returns an option that ignores all unexported fields in the given struct types.
Example:
result := CompareAggregatesWithOptions(
expected, actual,
IgnoreUnexportedFields(User{}, Profile{}),
)
func SortSlices ¶
SortSlices returns an option that sorts slices before comparison. Useful when slice order doesn't matter.
Example:
result := CompareAggregatesWithOptions(
expected, actual,
SortSlices(func(a, b string) bool { return a < b }),
)
Types ¶
type ComparisonResult ¶
ComparisonResult contains the result of comparing two values
func CompareAggregates ¶
func CompareAggregates(expected, actual any, excludePaths []string) *ComparisonResult
CompareAggregates deeply compares two aggregate states, excluding specified paths
This function uses Google's go-cmp library for robust, battle-tested comparison.
Parameters:
- expected: The expected aggregate state
- actual: The actual aggregate state to compare against
- excludePaths: List of paths to exclude from comparison (e.g., "Metadata.UpdatedAt", "ID")
Returns:
- ComparisonResult with Equal=true if states match, false otherwise
- Diff contains a human-readable diff if not equal
Example:
result := CompareAggregates(agg1.State(), agg2.State(), []string{"Metadata.Timestamp", "Version"})
if !result.Equal {
fmt.Println(result.Diff)
}
Path Format:
- Use dot notation for nested fields: "User.Profile.Email"
- Use wildcards for all fields in a struct: "Metadata.*"
- Use array notation for slices: "Items[0].Name"
func CompareAggregatesWithOptions ¶
func CompareAggregatesWithOptions(expected, actual any, opts ...cmp.Option) *ComparisonResult
CompareAggregatesWithOptions compares two aggregates with custom cmp.Options
This allows full control over the comparison behavior using go-cmp options.
Example:
opts := []cmp.Option{
cmpopts.IgnoreFields(User{}, "ID", "CreatedAt"),
cmpopts.EquateApproxTime(time.Second),
}
result := CompareAggregatesWithOptions(expected, actual, opts...)