package slug import ( "crypto/rand" "regexp" "strconv" "strings" "time" "unicode" "github.com/rs/zerolog/log" "golang.org/x/text/runes" "golang.org/x/text/transform" "golang.org/x/text/unicode/norm" ) func Generate(text string) string { log.Debug(). Str("service", "slugService"). Str("action", "generate_slug_started"). Str("input_text", text). Msg("starting slug generation") text = strings.ToLower(text) t := transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) text, _, _ = transform.String(t, text) text = strings.Replace(text, " ", "-", -1) text = strings.Replace(text, "_", "-", -1) reg := regexp.MustCompile(`[^a-z0-9-]+`) text = reg.ReplaceAllString(text, "") reg = regexp.MustCompile(`-+`) text = reg.ReplaceAllString(text, "-") text = strings.Trim(text, "-") if len(text) > 100 { log.Debug(). Str("service", "slugService"). Str("action", "slug_truncated"). Int("original_length", len(text)). Msg("slug exceeded 100 characters and was truncated") text = text[:100] text = strings.TrimRight(text, "-") } if text == "" { log.Warn(). Str("service", "slugService"). Str("action", "empty_slug_generated"). Str("input_text", text). Msg("slug is empty after sanitization; generating random fallback") text = "tenant" + generateRandomString(8) } return text } func GenerateUnique(base string, exists func(string) bool) string { log.Debug(). Str("service", "slugService"). Str("action", "generate_unique_slug_started"). Str("base", base). Msg("generating unique slug") slug := Generate(base) if !exists(slug) { log.Debug(). Str("service", "slugService"). Str("action", "unique_slug_available"). Str("slug", slug). Msg("slug is available without modification") return slug } for i := 2; i < 1000; i++ { candidate := slug + "-" + strconv.Itoa(i) if !exists(candidate) { log.Info(). Str("service", "slugService"). Str("action", "unique_slug_generated_with_suffix"). Str("slug", candidate). Int("attempt", i-1). Msg("slug collision resolved with numeric suffix") return candidate } } fallback := slug + "-" + strconv.Itoa(int(time.Now().Unix())) log.Warn(). Str("service", "slugService"). Str("action", "unique_slug_fallback_timestamp"). Str("slug", fallback). Msg("exhausted 1000 attempts; using timestamp fallback") return fallback } func generateRandomString(length int) string { log.Debug(). Str("service", "slugService"). Str("action", "generate_random_string"). Int("length", length). Msg("generating random slug string") const charset = "abcdefghijklmopqrstuvwxyz0123456789" randBytes := make([]byte, length) if _, err := rand.Read(randBytes); err != nil { log.Error(). Str("service", "slugService"). Str("action", "rand_read_failed"). Err(err). Msg("falling back to time-based random string") return strconv.FormatInt(time.Now().UnixNano(), 36)[:length] } result := make([]byte, length) for i := 0; i < length; i++ { result[i] = charset[randBytes[i]%byte(len(charset))] } str := string(result) log.Debug(). Str("service", "slugService"). Str("action", "random_string_generated"). Str("value", str). Msg("random slug string generated") return str }