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}