package ucxl import ( "fmt" "net/url" "regexp" "strings" ) // UCXLAddress represents a parsed UCXL address type UCXLAddress struct { Raw string `json:"raw"` Agent string `json:"agent"` // Agent ID or "*" for any Role string `json:"role"` // Agent role or "*" for any Project string `json:"project"` // Project identifier or "*" for any Task string `json:"task"` // Task identifier or "*" for any Path string `json:"path"` // Resource path (optional) Temporal string `json:"temporal"` // Temporal navigation (optional) } // UCXLAddressRegex matches valid UCXL address format // Format: ucxl://agent:role@project:task/path*temporal/ var UCXLAddressRegex = regexp.MustCompile(`^ucxl://([^:]+):([^@]+)@([^:]+):([^/]+)(/[^*]*)?(\*[^/]*)?/?$`) // ParseUCXLAddress parses a UCXL address string into its components func ParseUCXLAddress(address string) (*UCXLAddress, error) { if address == "" { return nil, fmt.Errorf("empty UCXL address") } // Check if it starts with ucxl:// if !strings.HasPrefix(address, "ucxl://") { return nil, fmt.Errorf("invalid UCXL address: must start with 'ucxl://'") } // Parse using regex matches := UCXLAddressRegex.FindStringSubmatch(address) if matches == nil { return nil, fmt.Errorf("invalid UCXL address format: %s", address) } ucxlAddr := &UCXLAddress{ Raw: address, Agent: matches[1], Role: matches[2], Project: matches[3], Task: matches[4], Path: strings.TrimPrefix(matches[5], "/"), Temporal: strings.TrimPrefix(matches[6], "*"), } // Validate components if err := validateUCXLComponents(ucxlAddr); err != nil { return nil, fmt.Errorf("invalid UCXL address: %w", err) } return ucxlAddr, nil } // validateUCXLComponents validates individual components of a UCXL address func validateUCXLComponents(addr *UCXLAddress) error { // Agent can be any non-empty string or "*" if addr.Agent == "" { return fmt.Errorf("agent cannot be empty") } // Role can be any non-empty string or "*" if addr.Role == "" { return fmt.Errorf("role cannot be empty") } // Project can be any non-empty string or "*" if addr.Project == "" { return fmt.Errorf("project cannot be empty") } // Task can be any non-empty string or "*" if addr.Task == "" { return fmt.Errorf("task cannot be empty") } // Path is optional, but if present should be valid if addr.Path != "" { // URL decode and validate path decoded, err := url.PathUnescape(addr.Path) if err != nil { return fmt.Errorf("invalid path encoding: %w", err) } addr.Path = decoded } // Temporal is optional if addr.Temporal != "" { // Validate temporal navigation syntax if !isValidTemporal(addr.Temporal) { return fmt.Errorf("invalid temporal navigation syntax: %s", addr.Temporal) } } return nil } // isValidTemporal validates temporal navigation syntax func isValidTemporal(temporal string) bool { // Valid temporal patterns: // ^/ - latest version // ~/ - earliest version // @timestamp - specific timestamp // ~n/ - n versions back // ^n/ - n versions forward validPatterns := []string{ `^\^/?$`, // ^/ or ^ `^~/?$`, // ~/ or ~ `^@\d+/?$`, // @timestamp `^~\d+/?$`, // ~n versions back `^\^\d+/?$`, // ^n versions forward } for _, pattern := range validPatterns { matched, _ := regexp.MatchString(pattern, temporal) if matched { return true } } return false } // GenerateUCXLAddress creates a UCXL address from components func GenerateUCXLAddress(agent, role, project, task, path, temporal string) (string, error) { // Validate required components if agent == "" || role == "" || project == "" || task == "" { return "", fmt.Errorf("agent, role, project, and task are required") } // Build address address := fmt.Sprintf("ucxl://%s:%s@%s:%s", agent, role, project, task) // Add path if provided if path != "" { if !strings.HasPrefix(path, "/") { path = "/" + path } // URL encode the path encodedPath := url.PathEscape(path) address += encodedPath } // Add temporal navigation if provided if temporal != "" { if !strings.HasPrefix(temporal, "*") { temporal = "*" + temporal } address += temporal } // Always end with / if !strings.HasSuffix(address, "/") { address += "/" } // Validate the generated address _, err := ParseUCXLAddress(address) if err != nil { return "", fmt.Errorf("generated invalid UCXL address: %w", err) } return address, nil } // IsValidUCXLAddress checks if a string is a valid UCXL address func IsValidUCXLAddress(address string) bool { _, err := ParseUCXLAddress(address) return err == nil } // NormalizeUCXLAddress normalizes a UCXL address to standard format func NormalizeUCXLAddress(address string) (string, error) { parsed, err := ParseUCXLAddress(address) if err != nil { return "", err } // Regenerate in standard format return GenerateUCXLAddress( parsed.Agent, parsed.Role, parsed.Project, parsed.Task, parsed.Path, parsed.Temporal, ) } // MatchesPattern checks if an address matches a pattern (supports wildcards) func (addr *UCXLAddress) MatchesPattern(pattern *UCXLAddress) bool { // Check agent if pattern.Agent != "*" && pattern.Agent != addr.Agent { return false } // Check role if pattern.Role != "*" && pattern.Role != addr.Role { return false } // Check project if pattern.Project != "*" && pattern.Project != addr.Project { return false } // Check task if pattern.Task != "*" && pattern.Task != addr.Task { return false } // Path matching (prefix-based if pattern ends with *) if pattern.Path != "" { if strings.HasSuffix(pattern.Path, "*") { prefix := strings.TrimSuffix(pattern.Path, "*") if !strings.HasPrefix(addr.Path, prefix) { return false } } else if pattern.Path != addr.Path { return false } } return true } // ToMap converts the UCXL address to a map for JSON serialization func (addr *UCXLAddress) ToMap() map[string]string { return map[string]string{ "raw": addr.Raw, "agent": addr.Agent, "role": addr.Role, "project": addr.Project, "task": addr.Task, "path": addr.Path, "temporal": addr.Temporal, } } // String returns the string representation of the UCXL address func (addr *UCXLAddress) String() string { return addr.Raw }