package auditcatalog import ( "errors" "fmt" "slices" "strings" ) type Cause uint8 const ( Request Cause = iota + 1 System Either ) type Actor string const ( ActorAdmin Actor = "admin" ActorUser Actor = "user" ActorSystem Actor = "system" ) type Subject string const ( SubjectUser Subject = "user" SubjectGroup Subject = "group" SubjectGroupName Subject = "group-name" SubjectInvitation Subject = "invitation" SubjectSession Subject = "session" SubjectLiteral Subject = "literal" ) const SentencePrefix = "audit.action." type Entry struct { Name string Actors []Actor Subjects []Subject Detail []string Cause Cause Sentence string } var registry = map[string]Entry{} func Register(entries ...Entry) bool { fresh := map[string]bool{} for _, e := range entries { if _, ok := registry[e.Name]; ok || fresh[e.Name] { panic(fmt.Sprintf("audit action %q registered twice", e.Name)) } if err := complete(e); err != nil { panic(fmt.Sprintf("audit action %q: %v", e.Name, err)) } fresh[e.Name] = true } for _, e := range entries { registry[e.Name] = e } return true } func complete(e Entry) error { switch { case e.Name == "": return errors.New("no name") case len(e.Actors) == 0: return errors.New("no actor kind") case len(e.Subjects) == 0: return errors.New("no subject kind") case e.Cause < Request || e.Cause > Either: return errors.New("no cause") case e.Sentence != SentencePrefix+e.Name: return fmt.Errorf("sentence key %q is not %q", e.Sentence, SentencePrefix+e.Name) } return nil } func Lookup(name string) (Entry, bool) { e, ok := registry[name] return e, ok } func Entries() []Entry { out := make([]Entry, 0, len(registry)) for _, e := range registry { out = append(out, e) } slices.SortFunc(out, func(a, b Entry) int { return strings.Compare(a.Name, b.Name) }) return out }