forked from tangled.org/core
this repo has no description
1package validator 2 3import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/bluesky-social/indigo/atproto/syntax" 9 "golang.org/x/exp/slices" 10 "tangled.sh/tangled.sh/core/api/tangled" 11 "tangled.sh/tangled.sh/core/appview/db" 12) 13 14var ( 15 // Label name should be alphanumeric with hyphens/underscores, but not start/end with them 16 labelNameRegex = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9_-]*[a-zA-Z0-9])?$`) 17 // Color should be a valid hex color 18 colorRegex = regexp.MustCompile(`^#[a-fA-F0-9]{6}$`) 19 // You can only label issues and pulls presently 20 validScopes = []syntax.NSID{tangled.RepoIssueNSID, tangled.RepoPullNSID} 21) 22 23func (v *Validator) ValidateLabelDefinition(label *db.LabelDefinition) error { 24 if label.Name == "" { 25 return fmt.Errorf("label name is empty") 26 } 27 if len(label.Name) > 40 { 28 return fmt.Errorf("label name too long (max 40 graphemes)") 29 } 30 if len(label.Name) < 1 { 31 return fmt.Errorf("label name too short (min 1 grapheme)") 32 } 33 if !labelNameRegex.MatchString(label.Name) { 34 return fmt.Errorf("label name contains invalid characters (use only letters, numbers, hyphens, and underscores)") 35 } 36 37 if !label.ValueType.IsConcreteType() { 38 return fmt.Errorf("invalid value type: %q (must be one of: null, boolean, integer, string)", label.ValueType) 39 } 40 41 if label.ValueType.IsNull() && label.ValueType.IsEnumType() { 42 return fmt.Errorf("null type cannot be used in conjunction with enum type") 43 } 44 45 // validate scope (nsid format) 46 if label.Scope == "" { 47 return fmt.Errorf("scope is required") 48 } 49 if _, err := syntax.ParseNSID(string(label.Scope)); err != nil { 50 return fmt.Errorf("failed to parse scope: %w", err) 51 } 52 if !slices.Contains(validScopes, label.Scope) { 53 return fmt.Errorf("invalid scope: scope must be one of %q", validScopes) 54 } 55 56 // validate color if provided 57 if label.Color != nil { 58 color := strings.TrimSpace(*label.Color) 59 if color == "" { 60 // empty color is fine, set to nil 61 label.Color = nil 62 } else { 63 if !colorRegex.MatchString(color) { 64 return fmt.Errorf("color must be a valid hex color (e.g. #79FFE1 or #000)") 65 } 66 // expand 3-digit hex to 6-digit hex 67 if len(color) == 4 { // #ABC 68 color = fmt.Sprintf("#%c%c%c%c%c%c", color[1], color[1], color[2], color[2], color[3], color[3]) 69 } 70 // convert to uppercase for consistency 71 color = strings.ToUpper(color) 72 label.Color = &color 73 } 74 } 75 76 return nil 77}