Add messaging config types and load/save functions (gt-i6jvc)
Add MessagingConfig with: - Lists: static mailing lists with fan-out delivery - Queues: shared work queues with claiming - Announces: bulletin boards for broadcast messages Includes: - types.go: MessagingConfig, QueueConfig, AnnounceConfig types - loader.go: Load/Save/Validate functions, MessagingConfigPath helper - loader_test.go: Round-trip and validation tests Created ~/gt/config/messaging.json with example configuration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -565,3 +565,111 @@ func expandPath(path string) string {
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// LoadMessagingConfig loads and validates a messaging configuration file.
|
||||
func LoadMessagingConfig(path string) (*MessagingConfig, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("%w: %s", ErrNotFound, path)
|
||||
}
|
||||
return nil, fmt.Errorf("reading messaging config: %w", err)
|
||||
}
|
||||
|
||||
var config MessagingConfig
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, fmt.Errorf("parsing messaging config: %w", err)
|
||||
}
|
||||
|
||||
if err := validateMessagingConfig(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// SaveMessagingConfig saves a messaging configuration to a file.
|
||||
func SaveMessagingConfig(path string, config *MessagingConfig) error {
|
||||
if err := validateMessagingConfig(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
||||
return fmt.Errorf("creating directory: %w", err)
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding messaging config: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(path, data, 0644); err != nil {
|
||||
return fmt.Errorf("writing messaging config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateMessagingConfig validates a MessagingConfig.
|
||||
func validateMessagingConfig(c *MessagingConfig) error {
|
||||
if c.Version > CurrentMessagingVersion {
|
||||
return fmt.Errorf("%w: got %d, max supported %d", ErrInvalidVersion, c.Version, CurrentMessagingVersion)
|
||||
}
|
||||
|
||||
// Initialize nil maps
|
||||
if c.Lists == nil {
|
||||
c.Lists = make(map[string][]string)
|
||||
}
|
||||
if c.Queues == nil {
|
||||
c.Queues = make(map[string]QueueConfig)
|
||||
}
|
||||
if c.Announces == nil {
|
||||
c.Announces = make(map[string]AnnounceConfig)
|
||||
}
|
||||
|
||||
// Validate lists have at least one recipient
|
||||
for name, recipients := range c.Lists {
|
||||
if len(recipients) == 0 {
|
||||
return fmt.Errorf("%w: list '%s' has no recipients", ErrMissingField, name)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate queues have at least one worker
|
||||
for name, queue := range c.Queues {
|
||||
if len(queue.Workers) == 0 {
|
||||
return fmt.Errorf("%w: queue '%s' has no workers", ErrMissingField, name)
|
||||
}
|
||||
if queue.MaxClaims < 0 {
|
||||
return fmt.Errorf("queue '%s': max_claims must be non-negative", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate announces have at least one reader
|
||||
for name, announce := range c.Announces {
|
||||
if len(announce.Readers) == 0 {
|
||||
return fmt.Errorf("%w: announce '%s' has no readers", ErrMissingField, name)
|
||||
}
|
||||
if announce.RetainCount < 0 {
|
||||
return fmt.Errorf("announce '%s': retain_count must be non-negative", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MessagingConfigPath returns the standard path for messaging config in a town.
|
||||
func MessagingConfigPath(townRoot string) string {
|
||||
return filepath.Join(townRoot, "config", "messaging.json")
|
||||
}
|
||||
|
||||
// LoadOrCreateMessagingConfig loads the messaging config, creating a default if not found.
|
||||
func LoadOrCreateMessagingConfig(path string) (*MessagingConfig, error) {
|
||||
config, err := LoadMessagingConfig(path)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return NewMessagingConfig(), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user