forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package validator
2
3import (
4 "fmt"
5 "maps"
6 "regexp"
7 "slices"
8 "strings"
9)
10
11const (
12 maxTopicLen = 50
13 maxTopics = 20
14)
15
16var (
17 topicRE = regexp.MustCompile(`\A[a-z0-9-]+\z`)
18)
19
20// ValidateRepoTopicStr parses and validates whitespace-separated topic string.
21//
22// Rules:
23// - topics are separated by whitespace
24// - each topic may contain lowercase letters, digits, and hyphens only
25// - each topic must be <= 50 characters long
26// - no more than 20 topics allowed
27// - duplicates are removed
28func (v *Validator) ValidateRepoTopicStr(topicsStr string) ([]string, error) {
29 topicsStr = strings.TrimSpace(topicsStr)
30 if topicsStr == "" {
31 return nil, nil
32 }
33 parts := strings.Fields(topicsStr)
34 if len(parts) > maxTopics {
35 return nil, fmt.Errorf("too many topics: %d (maximum %d)", len(parts), maxTopics)
36 }
37
38 topicSet := make(map[string]struct{})
39
40 for _, t := range parts {
41 if _, exists := topicSet[t]; exists {
42 continue
43 }
44 if len(t) > maxTopicLen {
45 return nil, fmt.Errorf("topic '%s' is too long (maximum %d characters)", t, maxTopics)
46 }
47 if !topicRE.MatchString(t) {
48 return nil, fmt.Errorf("topic '%s' contains invalid characters (allowed: lowercase letters, digits, hyphens)", t)
49 }
50 topicSet[t] = struct{}{}
51 }
52 return slices.Collect(maps.Keys(topicSet)), nil
53}