package protocol import ( "fmt" "net/url" "regexp" "strings" ) // BzzzURI represents a parsed bzzz:// URI with semantic addressing // Grammar: bzzz://[agent]:[role]@[project]:[task]/[path][?query][#fragment] type BzzzURI struct { // Core addressing components Agent string // Agent identifier (e.g., "claude", "any", "*") Role string // Agent role (e.g., "frontend", "backend", "architect") Project string // Project context (e.g., "chorus", "bzzz") Task string // Task identifier (e.g., "implement", "review", "test", "*") // Resource path Path string // Resource path (e.g., "/src/main.go", "/docs/api.md") // Standard URI components Query string // Query parameters Fragment string // Fragment identifier // Original raw URI string Raw string } // URI grammar constants const ( BzzzScheme = "bzzz" // Special identifiers AnyAgent = "any" AnyRole = "any" AnyProject = "any" AnyTask = "any" Wildcard = "*" ) // Validation patterns var ( // Component validation patterns agentPattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^\*$|^any$`) rolePattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^\*$|^any$`) projectPattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^\*$|^any$`) taskPattern = regexp.MustCompile(`^[a-zA-Z0-9\-_]+$|^\*$|^any$`) pathPattern = regexp.MustCompile(`^/[a-zA-Z0-9\-_/\.]*$|^$`) // Full URI pattern for validation bzzzURIPattern = regexp.MustCompile(`^bzzz://([a-zA-Z0-9\-_*]|any):([a-zA-Z0-9\-_*]|any)@([a-zA-Z0-9\-_*]|any):([a-zA-Z0-9\-_*]|any)(/[a-zA-Z0-9\-_/\.]*)?(\?[^#]*)?(\#.*)?$`) ) // ParseBzzzURI parses a bzzz:// URI string into a BzzzURI struct func ParseBzzzURI(uri string) (*BzzzURI, error) { if uri == "" { return nil, fmt.Errorf("empty URI") } // Basic scheme validation if !strings.HasPrefix(uri, BzzzScheme+"://") { return nil, fmt.Errorf("invalid scheme: expected '%s'", BzzzScheme) } // Use Go's standard URL parser for basic parsing parsedURL, err := url.Parse(uri) if err != nil { return nil, fmt.Errorf("failed to parse URI: %w", err) } if parsedURL.Scheme != BzzzScheme { return nil, fmt.Errorf("invalid scheme: expected '%s', got '%s'", BzzzScheme, parsedURL.Scheme) } // Parse the authority part (user:pass@host:port becomes agent:role@project:task) userInfo := parsedURL.User if userInfo == nil { return nil, fmt.Errorf("missing agent:role information") } username := userInfo.Username() password, hasPassword := userInfo.Password() if !hasPassword { return nil, fmt.Errorf("missing role information") } agent := username role := password // Parse host:port as project:task hostPort := parsedURL.Host if hostPort == "" { return nil, fmt.Errorf("missing project:task information") } // Split host:port to get project:task parts := strings.Split(hostPort, ":") if len(parts) != 2 { return nil, fmt.Errorf("invalid project:task format: expected 'project:task'") } project := parts[0] task := parts[1] // Create BzzzURI instance bzzzURI := &BzzzURI{ Agent: agent, Role: role, Project: project, Task: task, Path: parsedURL.Path, Query: parsedURL.RawQuery, Fragment: parsedURL.Fragment, Raw: uri, } // Validate components if err := bzzzURI.Validate(); err != nil { return nil, fmt.Errorf("validation failed: %w", err) } return bzzzURI, nil } // Validate validates all components of the BzzzURI func (u *BzzzURI) Validate() error { // Validate agent if u.Agent == "" { return fmt.Errorf("agent cannot be empty") } if !agentPattern.MatchString(u.Agent) { return fmt.Errorf("invalid agent format: '%s'", u.Agent) } // Validate role if u.Role == "" { return fmt.Errorf("role cannot be empty") } if !rolePattern.MatchString(u.Role) { return fmt.Errorf("invalid role format: '%s'", u.Role) } // Validate project if u.Project == "" { return fmt.Errorf("project cannot be empty") } if !projectPattern.MatchString(u.Project) { return fmt.Errorf("invalid project format: '%s'", u.Project) } // Validate task if u.Task == "" { return fmt.Errorf("task cannot be empty") } if !taskPattern.MatchString(u.Task) { return fmt.Errorf("invalid task format: '%s'", u.Task) } // Validate path (optional) if u.Path != "" && !pathPattern.MatchString(u.Path) { return fmt.Errorf("invalid path format: '%s'", u.Path) } return nil } // String returns the canonical string representation of the BzzzURI func (u *BzzzURI) String() string { uri := fmt.Sprintf("%s://%s:%s@%s:%s", BzzzScheme, u.Agent, u.Role, u.Project, u.Task) if u.Path != "" { uri += u.Path } if u.Query != "" { uri += "?" + u.Query } if u.Fragment != "" { uri += "#" + u.Fragment } return uri } // Normalize normalizes the URI components for consistent addressing func (u *BzzzURI) Normalize() { // Convert empty wildcards to standard wildcard if u.Agent == "" { u.Agent = Wildcard } if u.Role == "" { u.Role = Wildcard } if u.Project == "" { u.Project = Wildcard } if u.Task == "" { u.Task = Wildcard } // Normalize to lowercase for consistency u.Agent = strings.ToLower(u.Agent) u.Role = strings.ToLower(u.Role) u.Project = strings.ToLower(u.Project) u.Task = strings.ToLower(u.Task) // Clean path if u.Path != "" && !strings.HasPrefix(u.Path, "/") { u.Path = "/" + u.Path } } // IsWildcard checks if a component is a wildcard or "any" func IsWildcard(component string) bool { return component == Wildcard || component == AnyAgent || component == AnyRole || component == AnyProject || component == AnyTask } // Matches checks if this URI matches another URI (with wildcard support) func (u *BzzzURI) Matches(other *BzzzURI) bool { if other == nil { return false } // Check each component with wildcard support if !componentMatches(u.Agent, other.Agent) { return false } if !componentMatches(u.Role, other.Role) { return false } if !componentMatches(u.Project, other.Project) { return false } if !componentMatches(u.Task, other.Task) { return false } // Path matching (exact or wildcard) if u.Path != "" && other.Path != "" && u.Path != other.Path { return false } return true } // componentMatches checks if two components match (with wildcard support) func componentMatches(a, b string) bool { // Exact match if a == b { return true } // Wildcard matching if IsWildcard(a) || IsWildcard(b) { return true } return false } // GetSelectorPriority returns a priority score for URI matching (higher = more specific) func (u *BzzzURI) GetSelectorPriority() int { priority := 0 // More specific components get higher priority if !IsWildcard(u.Agent) { priority += 8 } if !IsWildcard(u.Role) { priority += 4 } if !IsWildcard(u.Project) { priority += 2 } if !IsWildcard(u.Task) { priority += 1 } // Path specificity adds priority if u.Path != "" && u.Path != "/" { priority += 1 } return priority } // ToAddress returns a simplified address representation for P2P routing func (u *BzzzURI) ToAddress() string { return fmt.Sprintf("%s:%s@%s:%s", u.Agent, u.Role, u.Project, u.Task) } // ValidateBzzzURIString validates a bzzz:// URI string without parsing func ValidateBzzzURIString(uri string) error { if uri == "" { return fmt.Errorf("empty URI") } if !bzzzURIPattern.MatchString(uri) { return fmt.Errorf("invalid bzzz:// URI format") } return nil } // NewBzzzURI creates a new BzzzURI with the given components func NewBzzzURI(agent, role, project, task, path string) *BzzzURI { uri := &BzzzURI{ Agent: agent, Role: role, Project: project, Task: task, Path: path, } uri.Normalize() return uri } // ParseAddress parses a simplified address format (agent:role@project:task) func ParseAddress(addr string) (*BzzzURI, error) { // Convert simplified address to full URI fullURI := BzzzScheme + "://" + addr return ParseBzzzURI(fullURI) }