1package syntax
2
3import (
4 "errors"
5 "regexp"
6)
7
8// Represents a Language specifier in string format, as would pass Lexicon syntax validation.
9//
10// Always use [ParseLanguage] instead of wrapping strings directly, especially when working with network input.
11//
12// The syntax is BCP-47. This is a partial/naive parsing implementation, designed for fast validation and exact-string passthrough with no normaliztion. For actually working with BCP-47 language specifiers in atproto code bases, we recommend the golang.org/x/text/language package.
13type Language string
14
15var langRegex = regexp.MustCompile(`^(i|[a-z]{2,3})(-[a-zA-Z0-9]+)*$`)
16
17func ParseLanguage(raw string) (Language, error) {
18 if raw == "" {
19 return "", errors.New("expected language code, got empty string")
20 }
21 if len(raw) > 128 {
22 return "", errors.New("Language is too long (128 chars max)")
23 }
24 if !langRegex.MatchString(raw) {
25 return "", errors.New("Language syntax didn't validate via regex")
26 }
27 return Language(raw), nil
28}
29
30func (l Language) String() string {
31 return string(l)
32}
33
34func (l Language) MarshalText() ([]byte, error) {
35 return []byte(l.String()), nil
36}
37
38func (l *Language) UnmarshalText(text []byte) error {
39 lang, err := ParseLanguage(string(text))
40 if err != nil {
41 return err
42 }
43 *l = lang
44 return nil
45}