package tenant import ( "fmt" "regexp" ) // Route describes a SaaS-owned exact Topic binding. Dispatcher only // consumes/publishes; it never declares, binds, or deletes these resources. type Route struct { Exchange string BindingKey string Queue string } var taskIDPattern = regexp.MustCompile(`^[A-Za-z0-9_-]{1,128}$`) func ControlRoute(dispatcherID string) (Route, error) { if err := ValidateDispatcherID(dispatcherID); err != nil { return Route{}, err } return exactRoute("agent-call.dispatchers.v1", "d."+dispatcherID+".control.in", "agent-call.d."+dispatcherID+".control.v1") } func TaskRoute(dispatcherID, taskID string) (Route, error) { if err := ValidateDispatcherID(dispatcherID); err != nil { return Route{}, err } if !taskIDPattern.MatchString(taskID) { return Route{}, fmt.Errorf("invalid task ID for exact MQ route") } return exactRoute("agent-call.dispatchers.v1", "d."+dispatcherID+".task."+taskID+".in", "agent-call.d."+dispatcherID+".task."+taskID+".v1") } func ResultRoute(dispatcherID string) (Route, error) { if err := ValidateDispatcherID(dispatcherID); err != nil { return Route{}, err } // The shared queue name is only a project-local Mock fixture. SaaS owns // the actual receiving queue and exact bindings for each Dispatcher. return exactRoute("agent-call.saas.v1", "d."+dispatcherID+".out", "agent-call.saas.events.v1") } func exactRoute(exchange, key, queue string) (Route, error) { if len(key) > 255 || len(queue) > 255 { return Route{}, fmt.Errorf("AMQP route exceeds 255 bytes") } return Route{Exchange: exchange, BindingKey: key, Queue: queue}, nil }