Commit f60c265

Eric Bower  ·  2025-12-26 10:53:17 -0500 EST
parent f1dc8fc
refactor: use `resolveTopic` to determine topic name
2 files changed,  +158, -146
+80, -146
......@@ -9,7 +9,6 @@ import (
99 "log/slog"
1010 "slices"
1111 "strings"
12- "text/tabwriter"
1312 "time"
1413
1514 "github.com/antoniomika/syncmap"
......@@ -321,42 +320,46 @@ func (handler *CliHandler) pub(cmd *CliCmd, topic string, clientID string) error
321320 topic = uuid.NewString()
322321 }
323322
324- var withoutUser string
325- var name string
326323 msgFlag := ""
327-
328- if cmd.isAdmin && strings.HasPrefix(topic, "/") {
329- name = strings.TrimPrefix(topic, "/")
330- } else {
331- name = toTopic(cmd.userName, topic)
332- if *public {
333- name = toPublicTopic(topic)
334- msgFlag = "-p "
335- withoutUser = name
336- } else {
337- withoutUser = topic
338- }
324+ if *public {
325+ msgFlag = "-p "
339326 }
340327
328+ // Initial resolution to get the topic name for access storage
329+ initialResult := resolveTopic(TopicResolveInput{
330+ UserName: cmd.userName,
331+ Topic: topic,
332+ IsAdmin: cmd.isAdmin,
333+ IsPublic: *public,
334+ })
335+ name := initialResult.Name
336+
341337 var accessListCreator bool
342338 _, loaded := handler.Access.LoadOrStore(name, accessList)
343339 if !loaded {
344340 defer func() {
345341 handler.Access.Delete(name)
346342 }()
347-
348343 accessListCreator = true
349344 }
350345
351- if accessList, ok := handler.Access.Load(withoutUser); ok && len(accessList) > 0 && !cmd.isAdmin {
352- if checkAccess(accessList, cmd.userName, cmd.sesh) || accessListCreator {
353- name = withoutUser
354- } else if !*public {
355- name = toTopic(cmd.userName, withoutUser)
356- } else {
357- topic = uuid.NewString()
358- name = toPublicTopic(topic)
359- }
346+ // Check for existing access list and resolve final topic name
347+ existingAccessList, hasExistingAccess := handler.Access.Load(initialResult.WithoutUser)
348+ result := resolveTopic(TopicResolveInput{
349+ UserName: cmd.userName,
350+ Topic: topic,
351+ IsAdmin: cmd.isAdmin,
352+ IsPublic: *public,
353+ ExistingAccessList: existingAccessList,
354+ HasExistingAccess: hasExistingAccess,
355+ IsAccessCreator: accessListCreator,
356+ HasUserAccess: checkAccess(existingAccessList, cmd.userName, cmd.sesh),
357+ })
358+ name = result.Name
359+
360+ if result.GenerateNewTopic {
361+ topic = uuid.NewString()
362+ name = toPublicTopic(topic)
360363 }
361364
362365 if !*clean {
......@@ -524,20 +527,14 @@ func (handler *CliHandler) sub(cmd *CliCmd, topic string, clientID string) error
524527 accessList = parseArgList(*access)
525528 }
526529
527- var withoutUser string
528- var name string
529-
530- if cmd.isAdmin && strings.HasPrefix(topic, "/") {
531- name = strings.TrimPrefix(topic, "/")
532- } else {
533- name = toTopic(cmd.userName, topic)
534- if *public {
535- name = toPublicTopic(topic)
536- withoutUser = name
537- } else {
538- withoutUser = topic
539- }
540- }
530+ // Initial resolution to get the topic name for access storage
531+ initialResult := resolveTopic(TopicResolveInput{
532+ UserName: cmd.userName,
533+ Topic: topic,
534+ IsAdmin: cmd.isAdmin,
535+ IsPublic: *public,
536+ })
537+ name := initialResult.Name
541538
542539 var accessListCreator bool
543540
......@@ -549,14 +546,22 @@ func (handler *CliHandler) sub(cmd *CliCmd, topic string, clientID string) error
549546 accessListCreator = true
550547 }
551548
552- if accessList, ok := handler.Access.Load(withoutUser); ok && len(accessList) > 0 && !cmd.isAdmin {
553- if checkAccess(accessList, cmd.userName, cmd.sesh) || accessListCreator {
554- name = withoutUser
555- } else if !*public {
556- name = toTopic(cmd.userName, withoutUser)
557- } else {
558- return fmt.Errorf("access denied")
559- }
549+ // Check for existing access list and resolve final topic name
550+ existingAccessList, hasExistingAccess := handler.Access.Load(initialResult.WithoutUser)
551+ result := resolveTopic(TopicResolveInput{
552+ UserName: cmd.userName,
553+ Topic: topic,
554+ IsAdmin: cmd.isAdmin,
555+ IsPublic: *public,
556+ ExistingAccessList: existingAccessList,
557+ HasExistingAccess: hasExistingAccess,
558+ IsAccessCreator: accessListCreator,
559+ HasUserAccess: checkAccess(existingAccessList, cmd.userName, cmd.sesh),
560+ })
561+ name = result.Name
562+
563+ if result.AccessDenied {
564+ return fmt.Errorf("access denied")
560565 }
561566
562567 err := handler.PubSub.Sub(
......@@ -612,23 +617,20 @@ func (handler *CliHandler) pipe(cmd *CliCmd, topic string, clientID string) erro
612617 topic = uuid.NewString()
613618 }
614619
615- var withoutUser string
616- var name string
617620 flagMsg := ""
618-
619- if cmd.isAdmin && strings.HasPrefix(topic, "/") {
620- name = strings.TrimPrefix(topic, "/")
621- } else {
622- name = toTopic(cmd.userName, topic)
623- if *public {
624- name = toPublicTopic(topic)
625- flagMsg = "-p "
626- withoutUser = name
627- } else {
628- withoutUser = topic
629- }
621+ if *public {
622+ flagMsg = "-p "
630623 }
631624
625+ // Initial resolution to get the topic name for access storage
626+ initialResult := resolveTopic(TopicResolveInput{
627+ UserName: cmd.userName,
628+ Topic: topic,
629+ IsAdmin: cmd.isAdmin,
630+ IsPublic: *public,
631+ })
632+ name := initialResult.Name
633+
632634 var accessListCreator bool
633635
634636 _, loaded := handler.Access.LoadOrStore(name, accessList)
......@@ -639,15 +641,23 @@ func (handler *CliHandler) pipe(cmd *CliCmd, topic string, clientID string) erro
639641 accessListCreator = true
640642 }
641643
642- if accessList, ok := handler.Access.Load(withoutUser); ok && len(accessList) > 0 && !cmd.isAdmin {
643- if checkAccess(accessList, cmd.userName, cmd.sesh) || accessListCreator {
644- name = withoutUser
645- } else if !*public {
646- name = toTopic(cmd.userName, withoutUser)
647- } else {
648- topic = uuid.NewString()
649- name = toPublicTopic(topic)
650- }
644+ // Check for existing access list and resolve final topic name
645+ existingAccessList, hasExistingAccess := handler.Access.Load(initialResult.WithoutUser)
646+ result := resolveTopic(TopicResolveInput{
647+ UserName: cmd.userName,
648+ Topic: topic,
649+ IsAdmin: cmd.isAdmin,
650+ IsPublic: *public,
651+ ExistingAccessList: existingAccessList,
652+ HasExistingAccess: hasExistingAccess,
653+ IsAccessCreator: accessListCreator,
654+ HasUserAccess: checkAccess(existingAccessList, cmd.userName, cmd.sesh),
655+ })
656+ name = result.Name
657+
658+ if result.GenerateNewTopic {
659+ topic = uuid.NewString()
660+ name = toPublicTopic(topic)
651661 }
652662
653663 if isCreator && !*clean {
......@@ -740,82 +750,6 @@ func flagCheck(cmd *flag.FlagSet, posArg string, cmdArgs []string) bool {
740750 return true
741751 }
742752
743-func NewTabWriter(out io.Writer) *tabwriter.Writer {
744- return tabwriter.NewWriter(out, 0, 0, 1, ' ', tabwriter.TabIndent)
745-}
746-
747-// scope topic to user by prefixing name.
748-func toTopic(userName, topic string) string {
749- if strings.HasPrefix(topic, userName+"/") {
750- return topic
751- }
752- return fmt.Sprintf("%s/%s", userName, topic)
753-}
754-
755-func toPublicTopic(topic string) string {
756- if strings.HasPrefix(topic, "public/") {
757- return topic
758- }
759- return fmt.Sprintf("public/%s", topic)
760-}
761-
762-// TopicResolveInput contains all inputs needed for topic resolution.
763-type TopicResolveInput struct {
764- UserName string
765- Topic string
766- IsAdmin bool
767- IsPublic bool
768- AccessList []string
769- ExistingAccessList []string
770- HasExistingAccess bool
771- IsAccessCreator bool
772- HasUserAccess bool
773-}
774-
775-// TopicResolveOutput contains the resolved topic name and any error.
776-type TopicResolveOutput struct {
777- Name string
778- WithoutUser string
779- AccessDenied bool
780- GenerateNewTopic bool
781-}
782-
783-// resolveTopic determines the final topic name based on user, flags, and access control.
784-func resolveTopic(input TopicResolveInput) TopicResolveOutput {
785- var name string
786- var withoutUser string
787-
788- if input.IsAdmin && strings.HasPrefix(input.Topic, "/") {
789- name = strings.TrimPrefix(input.Topic, "/")
790- return TopicResolveOutput{Name: name, WithoutUser: withoutUser}
791- }
792-
793- name = toTopic(input.UserName, input.Topic)
794- if input.IsPublic {
795- name = toPublicTopic(input.Topic)
796- withoutUser = name
797- } else {
798- withoutUser = input.Topic
799- }
800-
801- if input.HasExistingAccess && len(input.ExistingAccessList) > 0 && !input.IsAdmin {
802- if input.HasUserAccess || input.IsAccessCreator {
803- name = withoutUser
804- } else if !input.IsPublic {
805- name = toTopic(input.UserName, withoutUser)
806- } else {
807- return TopicResolveOutput{
808- Name: name,
809- WithoutUser: withoutUser,
810- AccessDenied: true,
811- GenerateNewTopic: true,
812- }
813- }
814- }
815-
816- return TopicResolveOutput{Name: name, WithoutUser: withoutUser}
817-}
818-
819753 func clientInfo(clients []*psub.Client, isAdmin bool, clientType string) string {
820754 if len(clients) == 0 {
821755 return ""
+78, -0
......@@ -0,0 +1,78 @@
1+package pipe
2+
3+import (
4+ "fmt"
5+ "strings"
6+)
7+
8+// toTopic scopes a topic to user by prefixing name.
9+func toTopic(userName, topic string) string {
10+ if strings.HasPrefix(topic, userName+"/") {
11+ return topic
12+ }
13+ return fmt.Sprintf("%s/%s", userName, topic)
14+}
15+
16+func toPublicTopic(topic string) string {
17+ if strings.HasPrefix(topic, "public/") {
18+ return topic
19+ }
20+ return fmt.Sprintf("public/%s", topic)
21+}
22+
23+// TopicResolveInput contains all inputs needed for topic resolution.
24+type TopicResolveInput struct {
25+ UserName string
26+ Topic string
27+ IsAdmin bool
28+ IsPublic bool
29+ AccessList []string
30+ ExistingAccessList []string
31+ HasExistingAccess bool
32+ IsAccessCreator bool
33+ HasUserAccess bool
34+}
35+
36+// TopicResolveOutput contains the resolved topic name and any error.
37+type TopicResolveOutput struct {
38+ Name string
39+ WithoutUser string
40+ AccessDenied bool
41+ GenerateNewTopic bool
42+}
43+
44+// resolveTopic determines the final topic name based on user, flags, and access control.
45+func resolveTopic(input TopicResolveInput) TopicResolveOutput {
46+ var name string
47+ var withoutUser string
48+
49+ if input.IsAdmin && strings.HasPrefix(input.Topic, "/") {
50+ name = strings.TrimPrefix(input.Topic, "/")
51+ return TopicResolveOutput{Name: name, WithoutUser: withoutUser}
52+ }
53+
54+ name = toTopic(input.UserName, input.Topic)
55+ if input.IsPublic {
56+ name = toPublicTopic(input.Topic)
57+ withoutUser = name
58+ } else {
59+ withoutUser = input.Topic
60+ }
61+
62+ if input.HasExistingAccess && len(input.ExistingAccessList) > 0 && !input.IsAdmin {
63+ if input.HasUserAccess || input.IsAccessCreator {
64+ name = withoutUser
65+ } else if !input.IsPublic {
66+ name = toTopic(input.UserName, withoutUser)
67+ } else {
68+ return TopicResolveOutput{
69+ Name: name,
70+ WithoutUser: withoutUser,
71+ AccessDenied: true,
72+ GenerateNewTopic: true,
73+ }
74+ }
75+ }
76+
77+ return TopicResolveOutput{Name: name, WithoutUser: withoutUser}
78+}