package commandkit import ( "errors" "fmt" ) // Register adds a batch of declarations to the application. The complete batch // is validated before registry mutation; on error the registry and its derived // command metadata remain unchanged. A successful nonempty batch becomes // visible to later invocations. Declarations remain reusable. func (a *App) Register(declarations ...Declaration) error { return a.installBatch(declarations...) } func (a *App) installBatch(declarations ...Declaration) error { if a == nil { return fmt.Errorf("cannot register commands on a nil app") } commands := make([]Command, 0, len(declarations)) rules := make([]RoutingRule, 0) for _, declaration := range declarations { switch value := declaration.(type) { case Command: commands = append(commands, value) case *Command: if value == nil { return fmt.Errorf("nil *Command declaration") } commands = append(commands, *value) case RoutingRule: rules = append(rules, value) case *RoutingRule: if value == nil { return fmt.Errorf("nil *RoutingRule declaration") } rules = append(rules, *value) case nil: return fmt.Errorf("nil declaration") default: return fmt.Errorf("unsupported declaration type %T", declaration) } } pending := make(map[string]*commandNode, len(commands)) for _, command := range commands { if err := validateCommandPlacement(command); err != nil { return err } if command.record.absent { continue } name := command.record.name if _, exists := a.commands[name]; exists { return fmt.Errorf("command path %q is already registered", name) } if _, exists := pending[name]; exists { return fmt.Errorf("command path %q is already registered", name) } pending[name] = &commandNode{command: command} } for _, rule := range rules { if rule.record == nil { return fmt.Errorf("invalid zero routing rule declaration") } if len(rule.record.issues) > 0 { return fmt.Errorf("routing rule: %w", errors.Join(rule.record.issues...)) } } if len(pending) == 0 && len(rules) == 0 { return nil } if a.commands == nil { a.commands = make(map[string]*commandNode, len(pending)) } for name, node := range pending { a.commands[name] = node } a.routingRules = append(a.routingRules, rules...) if len(pending) > 0 { a.snapshot = nil } return nil }