@@ -891,7 +891,7 @@ fn escape_name(name: &str, scheme: &EscapingScheme) -> String {
891
891
} else if is_valid_legacy_char ( b, i) {
892
892
escaped. push ( b) ;
893
893
} else {
894
- escaped. push ( '_' ) ;
894
+ escaped. push_str ( "__" ) ;
895
895
}
896
896
}
897
897
}
@@ -901,14 +901,12 @@ fn escape_name(name: &str, scheme: &EscapingScheme) -> String {
901
901
}
902
902
escaped. push_str ( "U__" ) ;
903
903
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) {
905
907
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 ( ) ;
912
910
}
913
911
}
914
912
}
@@ -935,3 +933,145 @@ pub fn negotiate_escaping_scheme(
935
933
}
936
934
default_escaping_scheme
937
935
}
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