package internal import ( "errors" "fmt" "sort" ) // TopicSubscription internally represents single topic subscription. type TopicSubscription struct { // PubsubName is name of the pub/sub this message came from. PubsubName string `json:"pubsubname"` // Topic is the name of the topic. Topic string `json:"topic"` // Route is the route of the handler where HTTP topic events should be published (passed as Path in gRPC). Route string `json:"route,omitempty"` // Routes specify multiple routes where topic events should be sent. Routes *TopicRoutes `json:"routes,omitempty"` // Metadata is the subscription metadata. Metadata map[string]string `json:"metadata,omitempty"` // DeadLetterTopic is the name of the deadletter topic. DeadLetterTopic string `json:"deadLetterTopic"` } // TopicRoutes encapsulates the default route and multiple routing rules. type TopicRoutes struct { Rules []TopicRule `json:"rules,omitempty"` Default string `json:"default,omitempty"` // priority is used to track duplicate priorities where priority > 0. // when priority is not specified (0), then the order in which they // were added is used. priorities map[int]struct{} } // TopicRule represents a single routing rule. type TopicRule struct { // Match is the CEL expression to match on the CloudEvent envelope. Match string `json:"match"` // Path is the HTTP path to post the event to (passed as Path in gRPC). Path string `json:"path"` // priority is the optional priority order (low to high) for this rule. priority int `json:"-"` } // NewTopicSubscription creates a new `TopicSubscription`. func NewTopicSubscription(pubsubName, topic, deadLetterTopic string) *TopicSubscription { return &TopicSubscription{ //nolint:exhaustivestruct PubsubName: pubsubName, Topic: topic, DeadLetterTopic: deadLetterTopic, } } // SetMetadata sets the metadata for the subscription if not already set. // An error is returned if it is already set. func (s *TopicSubscription) SetMetadata(metadata map[string]string) error { if s.Metadata != nil { return fmt.Errorf("subscription for topic %s on pubsub %s already has metadata set", s.Topic, s.PubsubName) } s.Metadata = metadata return nil } // SetDefaultRoute sets the default route if not already set. // An error is returned if it is already set. func (s *TopicSubscription) SetDefaultRoute(path string) error { if s.Routes == nil { if s.Route != "" { return fmt.Errorf("subscription for topic %s on pubsub %s already has route %s", s.Topic, s.PubsubName, s.Route) } s.Route = path } else { if s.Routes.Default != "" { return fmt.Errorf("subscription for topic %s on pubsub %s already has route %s", s.Topic, s.PubsubName, s.Routes.Default) } s.Routes.Default = path } return nil } // AddRoutingRule adds a routing rule. // An error is returned if a there id a duplicate priority > 1. func (s *TopicSubscription) AddRoutingRule(path, match string, priority int) error { if path == "" { return errors.New("path is required for routing rules") } if s.Routes == nil { s.Routes = &TopicRoutes{ //nolint:exhaustivestruct Default: s.Route, priorities: map[int]struct{}{}, } s.Route = "" } if priority > 0 { if _, exists := s.Routes.priorities[priority]; exists { return fmt.Errorf("subscription for topic %s on pubsub %s already has a routing rule with priority %d", s.Topic, s.PubsubName, priority) } } s.Routes.Rules = append(s.Routes.Rules, TopicRule{ Match: match, Path: path, priority: priority, }) sort.SliceStable(s.Routes.Rules, func(i, j int) bool { return s.Routes.Rules[i].priority < s.Routes.Rules[j].priority }) if priority > 0 { s.Routes.priorities[priority] = struct{}{} } return nil }