1#![deny(
2    future_incompatible,
3    nonstandard_style,
4    rust_2018_idioms,
5    missing_docs,
6    trivial_casts,
7    trivial_numeric_casts,
8    unused_qualifications
9)]
10#![cfg_attr(test, deny(warnings))]
11
12mod iana_registry;
62#[cfg(feature = "serde")]
63mod serde;
64
65use crate::iana_registry::*;
66use std::error::Error;
67use std::fmt;
68use std::iter::once;
69use std::ops::Deref;
70use std::str::FromStr;
71use std::str::Split;
72
73#[derive(Eq, PartialEq, Debug, Clone, Hash)]
81pub struct LanguageTag {
82    serialization: String,
84    language_end: usize,
85    extlang_end: usize,
86    script_end: usize,
87    region_end: usize,
88    variant_end: usize,
89    extension_end: usize,
90}
91
92impl LanguageTag {
93    #[inline]
97    pub fn as_str(&self) -> &str {
98        &self.serialization
99    }
100
101    #[inline]
105    pub fn into_string(self) -> String {
106        self.serialization
107    }
108
109    #[inline]
118    pub fn primary_language(&self) -> &str {
119        &self.serialization[..self.language_end]
120    }
121
122    #[inline]
133    pub fn extended_language(&self) -> Option<&str> {
134        if self.language_end == self.extlang_end {
135            None
136        } else {
137            Some(&self.serialization[self.language_end + 1..self.extlang_end])
138        }
139    }
140
141    #[inline]
152    pub fn extended_language_subtags(&self) -> impl Iterator<Item = &str> {
153        self.extended_language().unwrap_or("").split_terminator('-')
154    }
155
156    #[inline]
166    pub fn full_language(&self) -> &str {
167        &self.serialization[..self.extlang_end]
168    }
169
170    #[inline]
179    pub fn script(&self) -> Option<&str> {
180        if self.extlang_end == self.script_end {
181            None
182        } else {
183            Some(&self.serialization[self.extlang_end + 1..self.script_end])
184        }
185    }
186
187    #[inline]
197    pub fn region(&self) -> Option<&str> {
198        if self.script_end == self.region_end {
199            None
200        } else {
201            Some(&self.serialization[self.script_end + 1..self.region_end])
202        }
203    }
204
205    #[inline]
214    pub fn variant(&self) -> Option<&str> {
215        if self.region_end == self.variant_end {
216            None
217        } else {
218            Some(&self.serialization[self.region_end + 1..self.variant_end])
219        }
220    }
221
222    #[inline]
231    pub fn variant_subtags(&self) -> impl Iterator<Item = &str> {
232        self.variant().unwrap_or("").split_terminator('-')
233    }
234
235    #[inline]
244    pub fn extension(&self) -> Option<&str> {
245        if self.variant_end == self.extension_end {
246            None
247        } else {
248            Some(&self.serialization[self.variant_end + 1..self.extension_end])
249        }
250    }
251
252    #[inline]
261    pub fn extension_subtags(&self) -> impl Iterator<Item = (char, &str)> {
262        match self.extension() {
263            Some(parts) => ExtensionsIterator::new(parts),
264            None => ExtensionsIterator::new(""),
265        }
266    }
267
268    #[inline]
278    pub fn private_use(&self) -> Option<&str> {
279        if self.serialization.starts_with("x-") {
280            Some(&self.serialization)
281        } else if self.extension_end == self.serialization.len() {
282            None
283        } else {
284            Some(&self.serialization[self.extension_end + 1..])
285        }
286    }
287
288    #[inline]
297    pub fn private_use_subtags(&self) -> impl Iterator<Item = &str> {
298        self.private_use()
299            .map(|part| &part[2..])
300            .unwrap_or("")
301            .split_terminator('-')
302    }
303
304    pub fn parse(input: &str) -> Result<Self, ParseError> {
321        if let Some(tag) = GRANDFATHEREDS
323            .iter()
324            .find(|record| record.eq_ignore_ascii_case(input))
325        {
326            Ok(tag_from_primary_language(*tag))
328        } else if input.starts_with("x-") || input.starts_with("X-") {
329            if !is_alphanumeric_or_dash(input) {
331                Err(ParseError::ForbiddenChar)
332            } else if input.len() == 2 {
333                Err(ParseError::EmptyPrivateUse)
334            } else {
335                Ok(tag_from_primary_language(input.to_ascii_lowercase()))
336            }
337        } else {
338            parse_language_tag(input)
339        }
340    }
341
342    pub fn validate(&self) -> Result<(), ValidationError> {
361        if self.serialization.starts_with("x-") {
366            return Ok(());
367        }
368
369        if is_in_str_slice_set(&GRANDFATHEREDS, &self.serialization) {
371            return Ok(());
372        }
373
374        if let Some(extended_language) = self.extended_language() {
377            if extended_language.contains('-') {
378                return Err(ValidationError::MultipleExtendedLanguageSubtags);
379            }
380        }
381
382        let primary_language = self.primary_language();
386        if !between(primary_language, "qaa", "qtz")
387            && !is_in_from_str_slice_set(&LANGUAGES, primary_language)
388        {
389            return Err(ValidationError::PrimaryLanguageNotInRegistry);
390        }
391        if let Some(extended_language) = self.extended_language() {
392            if let Some(extended_language_prefix) =
393                find_in_from_str_slice_map(&EXTLANGS, extended_language)
394            {
395                if !self.serialization.starts_with(extended_language_prefix) {
396                    return Err(ValidationError::WrongExtendedLanguagePrefix);
397                }
398            } else {
399                return Err(ValidationError::ExtendedLanguageNotInRegistry);
400            }
401        }
402        if let Some(script) = self.script() {
403            if !between(script, "Qaaa", "Qabx") && !is_in_from_str_slice_set(&SCRIPTS, script) {
404                return Err(ValidationError::ScriptNotInRegistry);
405            }
406        }
407        if let Some(region) = self.region() {
408            if !between(region, "QM", "QZ")
409                && !between(region, "XA", "XZ")
410                && !is_in_from_str_slice_set(®IONS, region)
411            {
412                return Err(ValidationError::RegionNotInRegistry);
413            }
414        }
415        for variant in self.variant_subtags() {
416            if let Some(variant_prefixes) = find_in_str_slice_map(&VARIANTS, variant) {
417                if !variant_prefixes
418                    .split(' ')
419                    .any(|prefix| self.serialization.starts_with(prefix))
420                {
421                    return Err(ValidationError::WrongVariantPrefix);
422                }
423            } else {
424                return Err(ValidationError::VariantNotInRegistry);
425            }
426        }
427
428        let with_duplicate_variant = self.variant_subtags().enumerate().any(|(id1, variant1)| {
430            self.variant_subtags()
431                .enumerate()
432                .any(|(id2, variant2)| id1 != id2 && variant1 == variant2)
433        });
434        if with_duplicate_variant {
435            return Err(ValidationError::DuplicateVariant);
436        }
437
438        if let Some(extension) = self.extension() {
440            let mut seen_extensions = AlphanumericLowerCharSet::new();
441            let with_duplicate_extension = extension.split('-').any(|subtag| {
442                if subtag.len() == 1 {
443                    let extension = subtag.chars().next().unwrap();
444                    if seen_extensions.contains(extension) {
445                        true
446                    } else {
447                        seen_extensions.insert(extension);
448                        false
449                    }
450                } else {
451                    false
452                }
453            });
454            if with_duplicate_extension {
455                return Err(ValidationError::DuplicateExtension);
456            }
457        }
458
459        Ok(())
460    }
461
462    pub fn is_valid(&self) -> bool {
465        self.validate().is_ok()
466    }
467
468    pub fn canonicalize(&self) -> Result<LanguageTag, ValidationError> {
486        if self.serialization.starts_with("x-") {
488            return Ok(self.clone());
489        }
490
491        if is_in_str_slice_set(&GRANDFATHEREDS, &self.serialization) {
493            return Ok(
494                if let Some(preferred_value) =
495                    find_in_str_slice_map(&GRANDFATHEREDS_PREFERRED_VALUE, &self.serialization)
496                {
497                    Self::parse(preferred_value).unwrap()
498                } else {
499                    self.clone()
500                },
501            );
502        }
503        if let Some(preferred_value) =
504            find_in_str_slice_map(&REDUNDANTS_PREFERRED_VALUE, &self.serialization)
505        {
506            return Ok(Self::parse(preferred_value).unwrap());
507        }
508        let mut primary_language = self.primary_language();
513        if let Some(preferred_value) =
514            find_in_from_str_slice_map(&LANGUAGES_PREFERRED_VALUE, primary_language)
515        {
516            primary_language = preferred_value;
517        }
518
519        let mut extended_language = None;
522        if let Some(extlang) = self.extended_language() {
523            if extlang.contains('-') {
525                return Err(ValidationError::MultipleExtendedLanguageSubtags);
526            }
527            if let Some(preferred_value) =
528                find_in_from_str_slice_map(&EXTLANGS_PREFERRED_VALUE, extlang)
529            {
530                primary_language = preferred_value;
531            } else {
532                extended_language = Some(extlang);
533            }
534        }
535
536        let mut serialization = String::with_capacity(self.serialization.len());
537        serialization.push_str(primary_language);
538        let language_end = serialization.len();
539        if let Some(extended_language) = extended_language {
540            serialization.push('-');
541            serialization.push_str(extended_language);
542        }
543        let extlang_end = serialization.len();
544
545        if let Some(script) = self.script() {
547            let script =
548                find_in_from_str_slice_map(&SCRIPTS_PREFERRED_VALUE, script).unwrap_or(script);
549
550            let match_suppress_script =
552                find_in_from_str_slice_map(&LANGUAGES_SUPPRESS_SCRIPT, primary_language)
553                    .filter(|suppress_script| *suppress_script == script)
554                    .is_some();
555            if !match_suppress_script {
556                serialization.push('-');
557                serialization.push_str(script);
558            }
559        }
560        let script_end = serialization.len();
561
562        if let Some(region) = self.region() {
564            serialization.push('-');
565            serialization.push_str(
566                find_in_from_str_slice_map(®IONS_PREFERRED_VALUE, region).unwrap_or(region),
567            );
568        }
569        let region_end = serialization.len();
570
571        for variant in self.variant_subtags() {
573            let variant =
574                *find_in_str_slice_map(&VARIANTS_PREFERRED_VALUE, variant).unwrap_or(&variant);
575            let variant_already_exists = serialization.split('-').any(|subtag| subtag == variant);
576            if !variant_already_exists {
577                serialization.push('-');
578                serialization.push_str(variant);
579            }
580        }
581        let variant_end = serialization.len();
582
583        if self.extension().is_some() {
586            let mut extensions: Vec<_> = self.extension_subtags().collect();
587            extensions.sort_unstable();
588            for (k, v) in extensions {
589                serialization.push('-');
590                serialization.push(k);
591                serialization.push('-');
592                serialization.push_str(v);
593            }
594        }
595        let extension_end = serialization.len();
596
597        if let Some(private_use) = self.private_use() {
599            serialization.push('-');
600            serialization.push_str(private_use);
601        }
602
603        Ok(LanguageTag {
604            serialization,
605            language_end,
606            extlang_end,
607            script_end,
608            region_end,
609            variant_end,
610            extension_end,
611        })
612    }
613
614    pub fn matches(&self, other: &LanguageTag) -> bool {
641        fn matches_option(a: Option<&str>, b: Option<&str>) -> bool {
642            match (a, b) {
643                (Some(a), Some(b)) => a == b,
644                (None, _) => true,
645                (_, None) => false,
646            }
647        }
648        fn matches_iter<'a>(
649            a: impl Iterator<Item = &'a str>,
650            b: impl Iterator<Item = &'a str>,
651        ) -> bool {
652            a.zip(b).all(|(x, y)| x == y)
653        }
654        assert!(self.is_language_range());
655        self.full_language() == other.full_language()
656            && matches_option(self.script(), other.script())
657            && matches_option(self.region(), other.region())
658            && matches_iter(self.variant_subtags(), other.variant_subtags())
659    }
660
661    pub fn is_language_range(&self) -> bool {
663        self.extension().is_none() && self.private_use().is_none()
664    }
665}
666
667impl FromStr for LanguageTag {
668    type Err = ParseError;
669
670    #[inline]
671    fn from_str(input: &str) -> Result<Self, ParseError> {
672        Self::parse(input)
673    }
674}
675
676impl fmt::Display for LanguageTag {
677    #[inline]
678    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
679        f.write_str(self.as_str())
680    }
681}
682
683fn tag_from_primary_language(tag: impl Into<String>) -> LanguageTag {
685    let serialization = tag.into();
686    let end = serialization.len();
687    LanguageTag {
688        serialization,
689        language_end: end,
690        extlang_end: end,
691        script_end: end,
692        region_end: end,
693        variant_end: end,
694        extension_end: end,
695    }
696}
697
698fn parse_language_tag(input: &str) -> Result<LanguageTag, ParseError> {
700    #[derive(PartialEq, Eq)]
701    enum State {
702        Start,
703        AfterLanguage,
704        AfterExtLang,
705        AfterScript,
706        AfterRegion,
707        InExtension { expected: bool },
708        InPrivateUse { expected: bool },
709    }
710
711    let mut serialization = String::with_capacity(input.len());
712
713    let mut state = State::Start;
714    let mut language_end = 0;
715    let mut extlang_end = 0;
716    let mut script_end = 0;
717    let mut region_end = 0;
718    let mut variant_end = 0;
719    let mut extension_end = 0;
720    let mut extlangs_count = 0;
721    for (subtag, end) in SubTagIterator::new(input) {
722        if subtag.is_empty() {
723            return Err(ParseError::EmptySubtag);
725        }
726        if subtag.len() > 8 {
727            return Err(ParseError::SubtagTooLong);
729        }
730        if state == State::Start {
731            if subtag.len() < 2 || !is_alphabetic(subtag) {
733                return Err(ParseError::InvalidLanguage);
734            }
735            language_end = end;
736            serialization.extend(to_lowercase(subtag));
737            if subtag.len() < 4 {
738                state = State::AfterLanguage;
740            } else {
741                state = State::AfterExtLang;
742            }
743        } else if let State::InPrivateUse { .. } = state {
744            if !is_alphanumeric(subtag) {
745                return Err(ParseError::InvalidSubtag);
746            }
747            serialization.push('-');
748            serialization.extend(to_lowercase(subtag));
749            state = State::InPrivateUse { expected: false };
750        } else if subtag == "x" || subtag == "X" {
751            if let State::InExtension { expected: true } = state {
753                return Err(ParseError::EmptyExtension);
754            }
755            serialization.push('-');
756            serialization.push('x');
757            state = State::InPrivateUse { expected: true };
758        } else if subtag.len() == 1 && is_alphanumeric(subtag) {
759            if let State::InExtension { expected: true } = state {
761                return Err(ParseError::EmptyExtension);
762            }
763            let extension_tag = subtag.chars().next().unwrap().to_ascii_lowercase();
764            serialization.push('-');
765            serialization.push(extension_tag);
766            state = State::InExtension { expected: true };
767        } else if let State::InExtension { .. } = state {
768            if !is_alphanumeric(subtag) {
769                return Err(ParseError::InvalidSubtag);
770            }
771            extension_end = end;
772            serialization.push('-');
773            serialization.extend(to_lowercase(subtag));
774            state = State::InExtension { expected: false };
775        } else if state == State::AfterLanguage && subtag.len() == 3 && is_alphabetic(subtag) {
776            extlangs_count += 1;
777            if extlangs_count > 3 {
778                return Err(ParseError::TooManyExtlangs);
779            }
780            extlang_end = end;
782            serialization.push('-');
783            serialization.extend(to_lowercase(subtag));
784        } else if (state == State::AfterLanguage || state == State::AfterExtLang)
785            && subtag.len() == 4
786            && is_alphabetic(subtag)
787        {
788            script_end = end;
790            serialization.push('-');
791            serialization.extend(to_uppercase_first(subtag));
792            state = State::AfterScript;
793        } else if (state == State::AfterLanguage
794            || state == State::AfterExtLang
795            || state == State::AfterScript)
796            && (subtag.len() == 2 && is_alphabetic(subtag)
797                || subtag.len() == 3 && is_numeric(subtag))
798        {
799            region_end = end;
801            serialization.push('-');
802            serialization.extend(to_uppercase(subtag));
803            state = State::AfterRegion;
804        } else if (state == State::AfterLanguage
805            || state == State::AfterExtLang
806            || state == State::AfterScript
807            || state == State::AfterRegion)
808            && is_alphanumeric(subtag)
809            && (subtag.len() >= 5 && is_alphabetic(&subtag[0..1])
810                || subtag.len() >= 4 && is_numeric(&subtag[0..1]))
811        {
812            variant_end = end;
814            serialization.push('-');
815            serialization.extend(to_lowercase(subtag));
816            state = State::AfterRegion;
817        } else {
818            return Err(ParseError::InvalidSubtag);
819        }
820    }
821
822    if let State::InExtension { expected: true } = state {
824        return Err(ParseError::EmptyExtension);
825    }
826    if let State::InPrivateUse { expected: true } = state {
827        return Err(ParseError::EmptyPrivateUse);
828    }
829
830    if extlang_end < language_end {
832        extlang_end = language_end;
833    }
834    if script_end < extlang_end {
835        script_end = extlang_end;
836    }
837    if region_end < script_end {
838        region_end = script_end;
839    }
840    if variant_end < region_end {
841        variant_end = region_end;
842    }
843    if extension_end < variant_end {
844        extension_end = variant_end;
845    }
846
847    Ok(LanguageTag {
848        serialization,
849        language_end,
850        extlang_end,
851        script_end,
852        region_end,
853        variant_end,
854        extension_end,
855    })
856}
857
858struct ExtensionsIterator<'a> {
859    input: &'a str,
860}
861
862impl<'a> ExtensionsIterator<'a> {
863    fn new(input: &'a str) -> Self {
864        Self { input }
865    }
866}
867
868impl<'a> Iterator for ExtensionsIterator<'a> {
869    type Item = (char, &'a str);
870
871    fn next(&mut self) -> Option<(char, &'a str)> {
872        let mut parts_iterator = self.input.split_terminator('-');
873        let singleton = parts_iterator.next()?.chars().next().unwrap();
874        let mut content_size: usize = 2;
875        for part in parts_iterator {
876            if part.len() == 1 {
877                let content = &self.input[2..content_size - 1];
878                self.input = &self.input[content_size..];
879                return Some((singleton, content));
880            } else {
881                content_size += part.len() + 1;
882            }
883        }
884        let result = self.input.get(2..).map(|content| (singleton, content));
885        self.input = "";
886        result
887    }
888}
889
890struct SubTagIterator<'a> {
891    split: Split<'a, char>,
892    position: usize,
893}
894
895impl<'a> SubTagIterator<'a> {
896    fn new(input: &'a str) -> Self {
897        Self {
898            split: input.split('-'),
899            position: 0,
900        }
901    }
902}
903
904impl<'a> Iterator for SubTagIterator<'a> {
905    type Item = (&'a str, usize);
906
907    fn next(&mut self) -> Option<(&'a str, usize)> {
908        let tag = self.split.next()?;
909        let tag_end = self.position + tag.len();
910        self.position = tag_end + 1;
911        Some((tag, tag_end))
912    }
913}
914
915struct AlphanumericLowerCharSet {
916    alphabetic_set: [bool; 26],
917    numeric_set: [bool; 10],
918}
919
920impl AlphanumericLowerCharSet {
921    fn new() -> Self {
922        Self {
923            alphabetic_set: [false; 26],
924            numeric_set: [false; 10],
925        }
926    }
927
928    fn contains(&mut self, c: char) -> bool {
929        if c.is_ascii_digit() {
930            self.numeric_set[char_sub(c, '0')]
931        } else if c.is_ascii_lowercase() {
932            self.alphabetic_set[char_sub(c, 'a')]
933        } else if c.is_ascii_uppercase() {
934            self.alphabetic_set[char_sub(c, 'A')]
935        } else {
936            false
937        }
938    }
939
940    fn insert(&mut self, c: char) {
941        if c.is_ascii_digit() {
942            self.numeric_set[char_sub(c, '0')] = true
943        } else if c.is_ascii_lowercase() {
944            self.alphabetic_set[char_sub(c, 'a')] = true
945        } else if c.is_ascii_uppercase() {
946            self.alphabetic_set[char_sub(c, 'A')] = true
947        }
948    }
949}
950
951fn char_sub(c1: char, c2: char) -> usize {
952    (c1 as usize) - (c2 as usize)
953}
954
955fn is_alphabetic(s: &str) -> bool {
956    s.chars().all(|x| x.is_ascii_alphabetic())
957}
958
959fn is_numeric(s: &str) -> bool {
960    s.chars().all(|x| x.is_ascii_digit())
961}
962
963fn is_alphanumeric(s: &str) -> bool {
964    s.chars().all(|x| x.is_ascii_alphanumeric())
965}
966
967fn is_alphanumeric_or_dash(s: &str) -> bool {
968    s.chars().all(|x| x.is_ascii_alphanumeric() || x == '-')
969}
970
971fn to_uppercase(s: &'_ str) -> impl Iterator<Item = char> + '_ {
972    s.chars().map(|c| c.to_ascii_uppercase())
973}
974
975fn to_uppercase_first(s: &'_ str) -> impl Iterator<Item = char> + '_ {
977    let mut chars = s.chars();
978    once(chars.next().unwrap().to_ascii_uppercase()).chain(chars.map(|c| c.to_ascii_lowercase()))
979}
980
981fn to_lowercase(s: &'_ str) -> impl Iterator<Item = char> + '_ {
982    s.chars().map(|c| c.to_ascii_lowercase())
983}
984
985#[derive(Clone, Debug, Eq, PartialEq)]
987pub enum ParseError {
988    EmptyExtension,
990    EmptyPrivateUse,
992    ForbiddenChar,
994    InvalidSubtag,
996    InvalidLanguage,
998    SubtagTooLong,
1000    EmptySubtag,
1002    TooManyExtlangs,
1004}
1005
1006impl Error for ParseError {}
1007
1008impl fmt::Display for ParseError {
1009    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1010        f.write_str(match self {
1011            Self::EmptyExtension => "if an extension subtag is present, it must not be empty",
1012            Self::EmptyPrivateUse => "if the `x` subtag is present, it must not be empty",
1013            Self::ForbiddenChar => "the langtag contains a char not allowed",
1014            Self::InvalidSubtag => "a subtag fails to parse, it does not match any other subtags",
1015            Self::InvalidLanguage => "the given language subtag is invalid",
1016            Self::SubtagTooLong => "a subtag may be eight characters in length at maximum",
1017            Self::EmptySubtag => "a subtag should not be empty",
1018            Self::TooManyExtlangs => "at maximum three extlangs are allowed",
1019        })
1020    }
1021}
1022
1023#[derive(Clone, Debug, Eq, PartialEq)]
1025pub enum ValidationError {
1026    DuplicateVariant,
1028    DuplicateExtension,
1030    MultipleExtendedLanguageSubtags,
1032    PrimaryLanguageNotInRegistry,
1034    ExtendedLanguageNotInRegistry,
1036    ScriptNotInRegistry,
1038    RegionNotInRegistry,
1040    VariantNotInRegistry,
1042    WrongExtendedLanguagePrefix,
1044    WrongVariantPrefix,
1046}
1047
1048impl Error for ValidationError {}
1049
1050impl fmt::Display for ValidationError {
1051    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1052        f.write_str(match self {
1053            Self::DuplicateVariant => {
1054                "the same variant subtag is only allowed once in a tag"
1055            }
1056            Self::DuplicateExtension => {
1057                "the same extension subtag is only allowed once in a tag"
1058            }
1059            Self::MultipleExtendedLanguageSubtags => {
1060                "only one extended language subtag is allowed"
1061            }
1062            Self::PrimaryLanguageNotInRegistry => {
1063                "the primary language is not in the IANA Language Subtag Registry"
1064            }
1065            Self::ExtendedLanguageNotInRegistry => {
1066                "the extended language is not in the IANA Language Subtag Registry"
1067            }
1068            Self::ScriptNotInRegistry => {
1069                "the script is not in the IANA Language Subtag Registry"
1070            }
1071            Self::RegionNotInRegistry => {
1072                "the region is not in the IANA Language Subtag Registry"
1073            }
1074            Self::VariantNotInRegistry => {
1075                "a variant is not in the IANA Language Subtag Registry"
1076            }
1077            Self::WrongExtendedLanguagePrefix => {
1078                "the primary language is not the expected extended language prefix from the IANA Language Subtag Registry"
1079            }
1080            Self::WrongVariantPrefix => {
1081                "the language tag has not one of the expected variant prefix from the IANA Language Subtag Registry"
1082            }
1083        })
1084    }
1085}
1086
1087fn between<T: Ord>(value: T, start: T, end: T) -> bool {
1088    start <= value && value <= end
1089}
1090
1091fn is_in_str_slice_set(slice: &[&'static str], value: &str) -> bool {
1092    slice.binary_search(&value).is_ok()
1093}
1094
1095fn is_in_from_str_slice_set<T: Copy + Ord + FromStr>(slice: &[T], value: &str) -> bool {
1096    match T::from_str(value) {
1097        Ok(key) => slice.binary_search(&key).is_ok(),
1098        Err(_) => false,
1099    }
1100}
1101
1102fn find_in_str_slice_map<'a, V>(slice: &'a [(&'static str, V)], value: &str) -> Option<&'a V> {
1103    if let Ok(position) = slice.binary_search_by_key(&value, |t| t.0) {
1104        Some(&slice[position].1)
1105    } else {
1106        None
1107    }
1108}
1109
1110fn find_in_from_str_slice_map<'a, K: Copy + Ord + FromStr, V: Deref<Target = str>>(
1111    slice: &'a [(K, V)],
1112    value: &str,
1113) -> Option<&'a str> {
1114    if let Ok(position) = slice.binary_search_by_key(&K::from_str(value).ok()?, |t| t.0) {
1115        Some(&*slice[position].1)
1116    } else {
1117        None
1118    }
1119}