@@ -891,7 +891,7 @@ fn escape_name(name: &str, scheme: &EscapingScheme) -> String {
891891 } else if is_valid_legacy_char ( b, i) {
892892 escaped. push ( b) ;
893893 } else {
894- escaped. push ( '_' ) ;
894+ escaped. push_str ( "__" ) ;
895895 }
896896 }
897897 }
@@ -901,14 +901,12 @@ fn escape_name(name: &str, scheme: &EscapingScheme) -> String {
901901 }
902902 escaped. push_str ( "U__" ) ;
903903 for ( i, b) in name. chars ( ) . enumerate ( ) {
904- if is_valid_legacy_char ( b, i) {
904+ if b == '_' {
905+ escaped. push_str ( "__" ) ;
906+ } else if is_valid_legacy_char ( b, i) {
905907 escaped. push ( b) ;
906- } else if !b. is_ascii ( ) {
907- escaped. push_str ( "_FFFD_" ) ;
908- } else if b as u32 <= 0xFF {
909- write ! ( escaped, "_{:02X}_" , b as u32 ) . unwrap ( ) ;
910- } else if b as u32 <= 0xFFFF {
911- write ! ( escaped, "_{:04X}_" , b as u32 ) . unwrap ( ) ;
908+ } else {
909+ write ! ( escaped, "_{:x}_" , b as i64 ) . unwrap ( ) ;
912910 }
913911 }
914912 }
@@ -935,3 +933,145 @@ pub fn negotiate_escaping_scheme(
935933 }
936934 default_escaping_scheme
937935}
936+
937+ #[ cfg( test) ]
938+ mod tests {
939+ use super :: * ;
940+
941+ #[ test]
942+ fn metric_name_is_legacy_valid ( ) {
943+ struct Scenario {
944+ input : & ' static str ,
945+ expected : bool ,
946+ }
947+
948+ let scenarios = vec ! [
949+ Scenario {
950+ input: "Avalid_23name" ,
951+ expected: true ,
952+ } ,
953+ Scenario {
954+ input: "_Avalid_23name" ,
955+ expected: true ,
956+ } ,
957+ Scenario {
958+ input: "1valid_23name" ,
959+ expected: false ,
960+ } ,
961+ Scenario {
962+ input: "avalid_23name" ,
963+ expected: true ,
964+ } ,
965+ Scenario {
966+ input: "Ava:lid_23name" ,
967+ expected: true ,
968+ } ,
969+ Scenario {
970+ input: "a lid_23name" ,
971+ expected: false ,
972+ } ,
973+ Scenario {
974+ input: ":leading_colon" ,
975+ expected: true ,
976+ } ,
977+ Scenario {
978+ input: "colon:in:the:middle" ,
979+ expected: true ,
980+ } ,
981+ Scenario {
982+ input: "" ,
983+ expected: false ,
984+ } ,
985+ Scenario {
986+ input: "aÅz" ,
987+ expected: false ,
988+ } ,
989+ ] ;
990+
991+ for scenario in scenarios {
992+ let result = is_valid_legacy_metric_name ( scenario. input ) ;
993+ assert_eq ! ( result, scenario. expected) ;
994+ }
995+ }
996+
997+ #[ test]
998+ fn test_escape_name ( ) {
999+ struct Scenario {
1000+ name : & ' static str ,
1001+ input : & ' static str ,
1002+ expected_underscores : & ' static str ,
1003+ expected_dots : & ' static str ,
1004+ expected_value : & ' static str ,
1005+ }
1006+
1007+ let scenarios = vec ! [
1008+ Scenario {
1009+ name: "empty string" ,
1010+ input: "" ,
1011+ expected_underscores: "" ,
1012+ expected_dots: "" ,
1013+ expected_value: "" ,
1014+ } ,
1015+ Scenario {
1016+ name: "legacy valid name" ,
1017+ input: "no:escaping_required" ,
1018+ expected_underscores: "no:escaping_required" ,
1019+ expected_dots: "no:escaping__required" ,
1020+ expected_value: "no:escaping_required" ,
1021+ } ,
1022+ Scenario {
1023+ name: "name with dots" ,
1024+ input: "mysystem.prod.west.cpu.load" ,
1025+ expected_underscores: "mysystem_prod_west_cpu_load" ,
1026+ expected_dots: "mysystem_dot_prod_dot_west_dot_cpu_dot_load" ,
1027+ expected_value: "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load" ,
1028+ } ,
1029+ Scenario {
1030+ name: "name with dots and underscore" ,
1031+ input: "mysystem.prod.west.cpu.load_total" ,
1032+ expected_underscores: "mysystem_prod_west_cpu_load_total" ,
1033+ expected_dots: "mysystem_dot_prod_dot_west_dot_cpu_dot_load__total" ,
1034+ expected_value: "U__mysystem_2e_prod_2e_west_2e_cpu_2e_load__total" ,
1035+ } ,
1036+ Scenario {
1037+ name: "name with dots and colon" ,
1038+ input: "http.status:sum" ,
1039+ expected_underscores: "http_status:sum" ,
1040+ expected_dots: "http_dot_status:sum" ,
1041+ expected_value: "U__http_2e_status:sum" ,
1042+ } ,
1043+ Scenario {
1044+ name: "name with spaces and emoji" ,
1045+ input: "label with 😱" ,
1046+ expected_underscores: "label_with__" ,
1047+ expected_dots: "label__with____" ,
1048+ expected_value: "U__label_20_with_20__1f631_" ,
1049+ } ,
1050+ Scenario {
1051+ name: "name with unicode characters > 0x100" ,
1052+ input: "花火" ,
1053+ expected_underscores: "__" ,
1054+ expected_dots: "____" ,
1055+ expected_value: "U___82b1__706b_" ,
1056+ } ,
1057+ Scenario {
1058+ name: "name with spaces and edge-case value" ,
1059+ input: "label with \u{0100} " ,
1060+ expected_underscores: "label_with__" ,
1061+ expected_dots: "label__with____" ,
1062+ expected_value: "U__label_20_with_20__100_" ,
1063+ } ,
1064+ ] ;
1065+
1066+ for scenario in scenarios {
1067+ let result = escape_name ( scenario. input , & EscapingScheme :: UnderscoreEscaping ) ;
1068+ assert_eq ! ( result, scenario. expected_underscores, "{}" , scenario. name) ;
1069+
1070+ let result = escape_name ( scenario. input , & EscapingScheme :: DotsEscaping ) ;
1071+ assert_eq ! ( result, scenario. expected_dots, "{}" , scenario. name) ;
1072+
1073+ let result = escape_name ( scenario. input , & EscapingScheme :: ValueEncodingEscaping ) ;
1074+ assert_eq ! ( result, scenario. expected_value, "{}" , scenario. name) ;
1075+ }
1076+ }
1077+ }
0 commit comments