package pushrules import ( "encoding/json" "errors" "slices" ) // Errors an edit returns for cases a caller has to tell apart. var ( // ErrNoSuchRule is a rule that does not exist. ErrNoSuchRule = errors.New("no such push rule") // ErrNoSuchAnchor is a before or after naming a rule that is not a // user rule of the same kind. ErrNoSuchAnchor = errors.New("the rule to place this one next to does not exist") ) // Put creates or replaces a user rule. // // With before or after it goes next to that rule, which must be a user // rule of the same kind. With neither, a new rule goes first among the // user rules of its kind and a replaced one keeps its place and its // enabled flag. Either way a user rule stays below .m.rule.master and above // every other default of its kind, which is where Merge puts them. func (rs *Ruleset) Put(kind Kind, rule Rule, before, after string) error { if IsReserved(rule.RuleID) { return invalid("rule IDs starting with '.' are reserved for the server's own rules") } if IsReserved(before) || IsReserved(after) { return ErrNoSuchAnchor } rules := slices.Clone(rs.Rules(kind)) existing := slices.IndexFunc(rules, func(r Rule) bool { return r.RuleID == rule.RuleID && !r.Default }) if existing >= 0 && before == "" && after == "" { rule.Enabled = rules[existing].Enabled rules[existing] = rule rs.SetRules(kind, rules) return nil } if existing >= 0 { rules = slices.Delete(rules, existing, existing+1) } var at int switch { case before != "" || after != "": anchor := before if anchor == "" { anchor = after } i := slices.IndexFunc(rules, func(r Rule) bool { return r.RuleID == anchor && !r.Default }) if i < 0 { return ErrNoSuchAnchor } at = i if before == "" { at = i + 1 } default: // First among the user rules: after any default that Merge puts // ahead of them. at = slices.IndexFunc(rules, func(r Rule) bool { return !r.Default || r.RuleID != RuleMaster }) if at < 0 { at = len(rules) } } rs.SetRules(kind, slices.Insert(rules, at, rule)) return nil } // Delete removes a user rule. A default rule cannot be deleted, only // switched off, and is reported as not there. func (rs *Ruleset) Delete(kind Kind, ruleID string) error { rules := rs.Rules(kind) i := slices.IndexFunc(rules, func(r Rule) bool { return r.RuleID == ruleID && !r.Default }) if i < 0 { return ErrNoSuchRule } rs.SetRules(kind, slices.Delete(slices.Clone(rules), i, i+1)) return nil } // SetEnabled switches a rule on or off. Default rules included. func (rs *Ruleset) SetEnabled(kind Kind, ruleID string, enabled bool) error { return rs.update(kind, ruleID, func(r *Rule) { r.Enabled = enabled }) } // SetActions replaces a rule's actions. Default rules included. func (rs *Ruleset) SetActions(kind Kind, ruleID string, actions []json.RawMessage) error { if err := ValidateActions(actions); err != nil { return err } return rs.update(kind, ruleID, func(r *Rule) { r.Actions = actions }) } func (rs *Ruleset) update(kind Kind, ruleID string, fn func(*Rule)) error { rules := slices.Clone(rs.Rules(kind)) i := slices.IndexFunc(rules, func(r Rule) bool { return r.RuleID == ruleID }) if i < 0 { return ErrNoSuchRule } fn(&rules[i]) rs.SetRules(kind, rules) return nil }