package config import ( "errors" "regexp" "strings" ) // Shell is the public presentation configuration, independent of deployment topology and auth. type Shell struct { Mode string `yaml:"mode" json:"mode"` DefaultLocale string `yaml:"defaultLocale" json:"defaultLocale"` Adapter string `yaml:"adapter,omitempty" json:"adapter,omitempty"` } // NormalizeDefaults resolves omitted installation settings before projecting them to the browser. func (s *Shell) NormalizeDefaults() error { if s.Mode == "" { s.Mode = "embedded" } if s.DefaultLocale == "" { s.DefaultLocale = "en" } if s.Mode != "embedded" && s.Mode != "full" { return errors.New("shell.mode must be full or embedded") } if !wellFormedLocale(s.DefaultLocale) { return errors.New("shell.defaultLocale must be a well-formed BCP47 language tag") } if s.Adapter != "" && s.Adapter != "omics-portal" { return errors.New("shell.adapter must be omics-portal") } if s.Mode == "full" { s.Adapter = "" return nil } if s.Adapter == "" { s.Adapter = "omics-portal" } return nil } // RFC 5646 section 2.1 grammar. Capture variants and extensions to reject duplicates. // Validate syntax without consulting a language registry or the frontend's available catalogs. var localePattern = regexp.MustCompile(`^(?:` + `(?:[a-z]{2,3}(?:-[a-z]{3}){0,3}|[a-z]{4}|[a-z]{5,8})` + // language / extlang `(?:-[a-z]{4})?(?:-(?:[a-z]{2}|[0-9]{3}))?` + // script / region `((?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*)` + // variants `((?:-[0-9a-wy-z](?:-[a-z0-9]{2,8})+)*)` + // extensions `(?:-x(?:-[a-z0-9]{1,8})+)?` + // private use after a language `|x(?:-[a-z0-9]{1,8})+)$`) // private use alone func wellFormedLocale(locale string) bool { // BCP47 is ASCII. Check before case folding, which could otherwise map Unicode to ASCII. for _, character := range locale { if character > 127 { return false } } tag := strings.ToLower(locale) // These grandfathered irregular tags do not match the normal language-tag grammar. switch tag { case "en-gb-oed", "i-ami", "i-bnn", "i-default", "i-enochian", "i-hak", "i-klingon", "i-lux", "i-mingo", "i-navajo", "i-pwn", "i-tao", "i-tay", "i-tsu", "sgn-be-fr", "sgn-be-nl", "sgn-ch-de": return true } parts := localePattern.FindStringSubmatch(tag) if parts == nil { return false } variants := make(map[string]bool) for _, variant := range strings.Split(strings.TrimPrefix(parts[1], "-"), "-") { if variants[variant] { return false } variants[variant] = true } singletons := make(map[string]bool) for _, subtag := range strings.Split(parts[2], "-") { if len(subtag) != 1 { continue } if singletons[subtag] { return false } singletons[subtag] = true } return true }