prometheus-client-mmap 0.24.4-x86_64-darwin → 0.24.5-x86_64-darwin

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11eecc761b0bd7cbcc8137e7097cb81595b0bbbe5b01058b1a601f01fd0405e9
4
- data.tar.gz: c9d175042f2a64f0735d5a4b973506c56690a555e20bb115062c207e4389ba01
3
+ metadata.gz: c288a4602307bbeedfe916df669c95d80d555f1c95beb7a2c946c58368c0f86b
4
+ data.tar.gz: 41b3bc2a51e0aee5de6d1511a9feb7285270c1d8366e683befdb1564a025f3f3
5
5
  SHA512:
6
- metadata.gz: 3f5428ee9d38050a212df5d313c73ac4734ee6d0d174608ee5b72cd1d8ba586db82b41d30f356373b33a350ce9fa291d515b4e965dde309b83ca449dcd0d0b6b
7
- data.tar.gz: e67748cf4f5d979b25068bf1d86fc828419301fc09425c40f54cb24aa0247dcf69aa9a295daca22b8458cd04597182603a62ae1b02e9195a88350c33c2efc885
6
+ metadata.gz: 7c4bb28ac28b8ca1b12716fe291516aeb9404b1fb553ce8c6aee0126554e2bcec8a9372cef93f2be4b429b09b32c7096f9c0fe8fbeb52876ccdd44d1d179a411
7
+ data.tar.gz: 1fce85abecdbdd8c0f85090eae0294b39f6cf00524dd67d950a046f8890ed31bb52ea0a5c9952c2c6b2aabc66918d766356c72683b4116731002e298b16fbac4
@@ -169,6 +169,8 @@ dependencies = [
169
169
  "nix",
170
170
  "rand",
171
171
  "rb-sys",
172
+ "serde",
173
+ "serde_json",
172
174
  "sha2",
173
175
  "smallvec",
174
176
  "tempfile",
@@ -252,6 +254,12 @@ dependencies = [
252
254
  "windows-sys 0.48.0",
253
255
  ]
254
256
 
257
+ [[package]]
258
+ name = "itoa"
259
+ version = "1.0.6"
260
+ source = "registry+https://github.com/rust-lang/crates.io-index"
261
+ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
262
+
255
263
  [[package]]
256
264
  name = "lazy_static"
257
265
  version = "1.4.0"
@@ -515,6 +523,12 @@ dependencies = [
515
523
  "windows-sys 0.48.0",
516
524
  ]
517
525
 
526
+ [[package]]
527
+ name = "ryu"
528
+ version = "1.0.13"
529
+ source = "registry+https://github.com/rust-lang/crates.io-index"
530
+ checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
531
+
518
532
  [[package]]
519
533
  name = "seq-macro"
520
534
  version = "0.3.3"
@@ -526,6 +540,31 @@ name = "serde"
526
540
  version = "1.0.159"
527
541
  source = "registry+https://github.com/rust-lang/crates.io-index"
528
542
  checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065"
543
+ dependencies = [
544
+ "serde_derive",
545
+ ]
546
+
547
+ [[package]]
548
+ name = "serde_derive"
549
+ version = "1.0.159"
550
+ source = "registry+https://github.com/rust-lang/crates.io-index"
551
+ checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585"
552
+ dependencies = [
553
+ "proc-macro2",
554
+ "quote",
555
+ "syn 2.0.13",
556
+ ]
557
+
558
+ [[package]]
559
+ name = "serde_json"
560
+ version = "1.0.96"
561
+ source = "registry+https://github.com/rust-lang/crates.io-index"
562
+ checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1"
563
+ dependencies = [
564
+ "itoa",
565
+ "ryu",
566
+ "serde",
567
+ ]
529
568
 
530
569
  [[package]]
531
570
  name = "sha2"
@@ -555,6 +594,9 @@ name = "smallvec"
555
594
  version = "1.10.0"
556
595
  source = "registry+https://github.com/rust-lang/crates.io-index"
557
596
  checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
597
+ dependencies = [
598
+ "serde",
599
+ ]
558
600
 
559
601
  [[package]]
560
602
  name = "syn"
@@ -13,7 +13,9 @@ memmap2 = "0.5"
13
13
  # v0.26 cannot be built on CentOS 7 https://github.com/nix-rust/nix/issues/1972
14
14
  nix = { version = "0.25", features = ["mman"] } # mman used for MsFlags
15
15
  rb-sys = "0.9"
16
- smallvec = "1.10"
16
+ serde = { version = "1.0", features = ["derive"] }
17
+ serde_json = { version = "1.0", features = ["raw_value"] }
18
+ smallvec = { version = "1.10", features = ["serde"] }
17
19
  thiserror = "1.0"
18
20
 
19
21
  [dev-dependencies]
@@ -1,10 +1,12 @@
1
1
  use magnus::Symbol;
2
+ use serde::Deserialize;
3
+ use serde_json::value::RawValue;
4
+ use smallvec::SmallVec;
2
5
  use std::fmt::Write;
3
6
  use std::str;
4
7
 
5
8
  use crate::error::{MmapError, RubyError};
6
9
  use crate::file_info::FileInfo;
7
- use crate::parser::{self, MetricText};
8
10
  use crate::raw_entry::RawEntry;
9
11
  use crate::Result;
10
12
  use crate::{SYM_GAUGE, SYM_LIVESUM, SYM_MAX, SYM_MIN};
@@ -16,6 +18,16 @@ pub struct FileEntry {
16
18
  pub meta: EntryMetadata,
17
19
  }
18
20
 
21
+ /// String slices pointing to the fields of a borrowed `Entry`'s JSON data.
22
+ #[derive(Deserialize, Debug)]
23
+ pub struct MetricText<'a> {
24
+ pub family_name: &'a str,
25
+ pub metric_name: &'a str,
26
+ pub labels: SmallVec<[&'a str; 4]>,
27
+ #[serde(borrow)]
28
+ pub values: SmallVec<[&'a RawValue; 4]>,
29
+ }
30
+
19
31
  /// The primary data payload for a `FileEntry`, the JSON string and the
20
32
  /// associated pid, if significant. Used as the key for `EntryMap`.
21
33
  #[derive(Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
@@ -134,11 +146,16 @@ impl FileEntry {
134
146
  let mut processed_count = 0;
135
147
 
136
148
  for entry in entries {
137
- let metrics_data = match parser::parse_metrics(&entry.data.json) {
138
- Some(m) => m,
149
+ let metrics_data = match serde_json::from_str::<MetricText>(&entry.data.json) {
150
+ Ok(m) => {
151
+ if m.labels.len() != m.values.len() {
152
+ continue;
153
+ }
154
+ m
155
+ }
139
156
  // We don't exit the function here so the total number of invalid
140
157
  // entries can be calculated below.
141
- None => continue,
158
+ Err(_) => continue,
142
159
  };
143
160
 
144
161
  match prev_name.as_ref() {
@@ -197,16 +214,20 @@ impl FileEntry {
197
214
 
198
215
  let it = json_data.labels.iter().zip(json_data.values.iter());
199
216
 
200
- for (i, (&key, &val)) in it.enumerate() {
217
+ for (i, (&key, val)) in it.enumerate() {
201
218
  out.push_str(key);
202
-
203
- out.push_str("=\"");
204
-
205
- // `null` values will be output as `""`.
206
- if val != "null" {
207
- out.push_str(val);
219
+ out.push('=');
220
+
221
+ match val.get() {
222
+ "null" => out.push_str("\"\""),
223
+ s if s.starts_with('"') => out.push_str(s),
224
+ s => {
225
+ // Quote numeric values.
226
+ out.push('"');
227
+ out.push_str(s);
228
+ out.push('"');
229
+ }
208
230
  }
209
- out.push('"');
210
231
 
211
232
  if i < json_data.labels.len() - 1 {
212
233
  out.push(',');
@@ -276,6 +297,20 @@ mod test {
276
297
  "##}),
277
298
  expected_err: None,
278
299
  },
300
+ TestCase {
301
+ name: "many labels",
302
+ multiprocess_mode: "min",
303
+ json: &[
304
+ r#"["family","name",["label_a","label_b","label_c","label_d","label_e"],["value_a","value_b","value_c","value_d","value_e"]]"#,
305
+ ],
306
+ values: &[1.0],
307
+ pids: &["worker-1"],
308
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
309
+ # TYPE family gauge
310
+ name{label_a="value_a",label_b="value_b",label_c="value_c",label_d="value_d",label_e="value_e"} 1
311
+ "##}),
312
+ expected_err: None,
313
+ },
279
314
  TestCase {
280
315
  name: "floating point shown",
281
316
  multiprocess_mode: "min",
@@ -288,6 +323,44 @@ mod test {
288
323
  "##}),
289
324
  expected_err: None,
290
325
  },
326
+ TestCase {
327
+ name: "numeric value",
328
+ multiprocess_mode: "min",
329
+ json: &[
330
+ r#"["family","name",["label_a","label_b","label_c"],["value_a",403,-0.2E5]]"#,
331
+ ],
332
+ values: &[1.5],
333
+ pids: &["worker-1"],
334
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
335
+ # TYPE family gauge
336
+ name{label_a="value_a",label_b="403",label_c="-0.2E5"} 1.5
337
+ "##}),
338
+ expected_err: None,
339
+ },
340
+ TestCase {
341
+ name: "null value",
342
+ multiprocess_mode: "min",
343
+ json: &[r#"["family","name",["label_a","label_b"],["value_a",null]]"#],
344
+ values: &[1.5],
345
+ pids: &["worker-1"],
346
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
347
+ # TYPE family gauge
348
+ name{label_a="value_a",label_b=""} 1.5
349
+ "##}),
350
+ expected_err: None,
351
+ },
352
+ TestCase {
353
+ name: "comma in value",
354
+ multiprocess_mode: "min",
355
+ json: &[r#"["family","name",["label_a","label_b"],["value_a","value,_b"]]"#],
356
+ values: &[1.5],
357
+ pids: &["worker-1"],
358
+ expected_out: Some(indoc! {r##"# HELP family Multiprocess metric
359
+ # TYPE family gauge
360
+ name{label_a="value_a",label_b="value,_b"} 1.5
361
+ "##}),
362
+ expected_err: None,
363
+ },
291
364
  TestCase {
292
365
  name: "no labels, pid significant",
293
366
  multiprocess_mode: "all",
@@ -413,6 +486,138 @@ mod test {
413
486
  RubyError::Runtime,
414
487
  )),
415
488
  },
489
+ TestCase {
490
+ name: "too many values",
491
+ multiprocess_mode: "min",
492
+ json: &[r#"["family","name",["label_a"],["value_a","value,_b"]]"#],
493
+ values: &[1.5],
494
+ pids: &["worker-1"],
495
+ expected_out: None,
496
+ expected_err: Some(MmapError::legacy(
497
+ "Processed entries 0 != map entries 1".to_owned(),
498
+ RubyError::Runtime,
499
+ )),
500
+ },
501
+ TestCase {
502
+ name: "no values",
503
+ multiprocess_mode: "min",
504
+ json: &[r#"["family","name",["label_a"]]"#],
505
+ values: &[1.5],
506
+ pids: &["worker-1"],
507
+ expected_out: None,
508
+ expected_err: Some(MmapError::legacy(
509
+ "Processed entries 0 != map entries 1".to_owned(),
510
+ RubyError::Runtime,
511
+ )),
512
+ },
513
+ TestCase {
514
+ name: "no labels or values",
515
+ multiprocess_mode: "min",
516
+ json: &[r#"["family","name","foo"]"#],
517
+ values: &[1.5],
518
+ pids: &["worker-1"],
519
+ expected_out: None,
520
+ expected_err: Some(MmapError::legacy(
521
+ "Processed entries 0 != map entries 1".to_owned(),
522
+ RubyError::Runtime,
523
+ )),
524
+ },
525
+ TestCase {
526
+ name: "too many leading brackets",
527
+ multiprocess_mode: "min",
528
+ json: &[r#"[["family","name",["label_a","label_b"],["value_a","value_b"]]"#],
529
+ values: &[1.5],
530
+ pids: &["worker-1"],
531
+ expected_out: None,
532
+ expected_err: Some(MmapError::legacy(
533
+ "Processed entries 0 != map entries 1".to_owned(),
534
+ RubyError::Runtime,
535
+ )),
536
+ },
537
+ TestCase {
538
+ name: "too many trailing brackets",
539
+ multiprocess_mode: "min",
540
+ json: &[r#"["family","name",["label_a","label_b"],["value_a","value_b"]]]"#],
541
+ values: &[1.5],
542
+ pids: &["worker-1"],
543
+ expected_out: None,
544
+ expected_err: Some(MmapError::legacy(
545
+ "Processed entries 0 != map entries 1".to_owned(),
546
+ RubyError::Runtime,
547
+ )),
548
+ },
549
+ TestCase {
550
+ name: "too many leading label brackets",
551
+ multiprocess_mode: "min",
552
+ json: &[r#"["family","name",[["label_a","label_b"],["value_a","value_b"]]"#],
553
+ values: &[1.5],
554
+ pids: &["worker-1"],
555
+ expected_out: None,
556
+ expected_err: Some(MmapError::legacy(
557
+ "Processed entries 0 != map entries 1".to_owned(),
558
+ RubyError::Runtime,
559
+ )),
560
+ },
561
+ TestCase {
562
+ name: "too many leading label brackets",
563
+ multiprocess_mode: "min",
564
+ json: &[r#"["family","name",[["label_a","label_b"],["value_a","value_b"]]"#],
565
+ values: &[1.5],
566
+ pids: &["worker-1"],
567
+ expected_out: None,
568
+ expected_err: Some(MmapError::legacy(
569
+ "Processed entries 0 != map entries 1".to_owned(),
570
+ RubyError::Runtime,
571
+ )),
572
+ },
573
+ TestCase {
574
+ name: "too many leading value brackets",
575
+ multiprocess_mode: "min",
576
+ json: &[r#"["family","name",["label_a","label_b"],[["value_a","value_b"]]"#],
577
+ values: &[1.5],
578
+ pids: &["worker-1"],
579
+ expected_out: None,
580
+ expected_err: Some(MmapError::legacy(
581
+ "Processed entries 0 != map entries 1".to_owned(),
582
+ RubyError::Runtime,
583
+ )),
584
+ },
585
+ TestCase {
586
+ name: "misplaced bracket",
587
+ multiprocess_mode: "min",
588
+ json: &[r#"["family","name",["label_a","label_b"],]["value_a","value_b"]]"#],
589
+ values: &[1.5],
590
+ pids: &["worker-1"],
591
+ expected_out: None,
592
+ expected_err: Some(MmapError::legacy(
593
+ "Processed entries 0 != map entries 1".to_owned(),
594
+ RubyError::Runtime,
595
+ )),
596
+ },
597
+ TestCase {
598
+ name: "comma in numeric",
599
+ multiprocess_mode: "min",
600
+ json: &[r#"["family","name",["label_a","label_b"],["value_a",403,0]]"#],
601
+ values: &[1.5],
602
+ pids: &["worker-1"],
603
+ expected_out: None,
604
+ expected_err: Some(MmapError::legacy(
605
+ "Processed entries 0 != map entries 1".to_owned(),
606
+ RubyError::Runtime,
607
+ )),
608
+ },
609
+ TestCase {
610
+ name: "non-e letter in numeric",
611
+ multiprocess_mode: "min",
612
+ json: &[r#"["family","name",["label_a","label_b"],["value_a",-2.0c5]]"#],
613
+ values: &[1.5],
614
+ pids: &["worker-1"],
615
+ expected_out: None,
616
+ expected_err: Some(MmapError::legacy(
617
+ "Processed entries 0 != map entries 1".to_owned(),
618
+ RubyError::Runtime,
619
+ )),
620
+ },
416
621
  ];
417
622
 
418
623
  for case in tc {
@@ -12,7 +12,6 @@ pub mod file_info;
12
12
  mod macros;
13
13
  pub mod map;
14
14
  pub mod mmap;
15
- pub mod parser;
16
15
  pub mod raw_entry;
17
16
  pub mod util;
18
17
 
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  module Prometheus
2
2
  module Client
3
- VERSION = '0.24.4'.freeze
3
+ VERSION = '0.24.5'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prometheus-client-mmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.4
4
+ version: 0.24.5
5
5
  platform: x86_64-darwin
6
6
  authors:
7
7
  - Tobias Schmidt
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2023-06-12 00:00:00.000000000 Z
14
+ date: 2023-06-13 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rb_sys
@@ -159,7 +159,6 @@ files:
159
159
  - ext/fast_mmaped_file_rs/src/map.rs
160
160
  - ext/fast_mmaped_file_rs/src/mmap.rs
161
161
  - ext/fast_mmaped_file_rs/src/mmap/inner.rs
162
- - ext/fast_mmaped_file_rs/src/parser.rs
163
162
  - ext/fast_mmaped_file_rs/src/raw_entry.rs
164
163
  - ext/fast_mmaped_file_rs/src/testhelper.rs
165
164
  - ext/fast_mmaped_file_rs/src/util.rs
@@ -1,448 +0,0 @@
1
- use smallvec::SmallVec;
2
- use std::str;
3
-
4
- /// String slices pointing to the fields of a borrowed `Entry`'s JSON data.
5
- #[derive(PartialEq, Eq, Debug)]
6
- pub struct MetricText<'a> {
7
- pub family_name: &'a str,
8
- pub metric_name: &'a str,
9
- pub labels: SmallVec<[&'a str; 4]>,
10
- pub values: SmallVec<[&'a str; 4]>,
11
- }
12
-
13
- #[derive(PartialEq, Eq, Debug)]
14
- struct MetricNames<'a> {
15
- label_json: &'a str,
16
- family_name: &'a str,
17
- metric_name: &'a str,
18
- }
19
-
20
- #[derive(PartialEq, Eq, Debug)]
21
- struct MetricLabelVals<'a> {
22
- labels: SmallVec<[&'a str; 4]>,
23
- values: SmallVec<[&'a str; 4]>,
24
- }
25
-
26
- /// Parse Prometheus metric data stored in the following format:
27
- ///
28
- /// ["metric","name",["label_a","label_b"],["value_a","value_b"]]
29
- ///
30
- /// There will be 1-8 trailing spaces to ensure at least a one-byte
31
- /// gap between the json and value, and to pad to an 8-byte alignment.
32
- /// We strip the surrounding double quotes from all items. Values may
33
- /// or may not have surrounding double quotes, depending on their type.
34
- pub fn parse_metrics(json: &str) -> Option<MetricText> {
35
- // It would be preferable to use `serde_json` here, but the values
36
- // may be strings, numbers, or null, and so don't parse easily to a
37
- // defined struct. Using `serde_json::Value` is an option, but since
38
- // we're just copying the literal values into a buffer this will be
39
- // inefficient and verbose.
40
-
41
- // Trim trailing spaces from string before processing. We use
42
- // `trim_end_matches()` instead of `trim_end()` because we know the
43
- // trailing bytes are always ASCII 0x20 bytes. `trim_end()` will also
44
- // check for unicode spaces and consume a few more CPU cycles.
45
- let trimmed = json.trim_end_matches(' ');
46
-
47
- let names = parse_names(trimmed)?;
48
- let label_json = names.label_json;
49
-
50
- let label_vals = parse_label_values(label_json)?;
51
-
52
- Some(MetricText {
53
- family_name: names.family_name,
54
- metric_name: names.metric_name,
55
- labels: label_vals.labels,
56
- values: label_vals.values,
57
- })
58
- }
59
-
60
- fn parse_names(json: &str) -> Option<MetricNames> {
61
- // Starting with: ["family_name","metric_name",[...
62
- if !json.starts_with("[\"") {
63
- return None;
64
- }
65
-
66
- // Captured: "family_name","metric_name",
67
- // ^^^^^^^^^^^
68
- let (family_name, remainder) = scan_str(json.get(1..)?)?;
69
-
70
- // Validate comma separated names
71
- // ["family_name","metric_name",[...
72
- // ^
73
- if !remainder.starts_with("\",") {
74
- return None;
75
- }
76
-
77
- // Now: "metric_name",[...
78
- let remainder = remainder.get(2..)?;
79
-
80
- // Captured: "family_name","metric_name",
81
- // ^^^^^^^^^^^
82
- let (metric_name, remainder) = scan_str(remainder)?;
83
-
84
- // Validate comma separated names
85
- // ["family_name","metric_name",[...
86
- // ^^
87
- if !remainder.starts_with("\",") {
88
- return None;
89
- }
90
-
91
- // Save the rest of the slice to parse for labels later.
92
- // Now: [...
93
- let label_json = remainder.get(2..)?;
94
-
95
- Some(MetricNames {
96
- label_json,
97
- family_name,
98
- metric_name,
99
- })
100
- }
101
-
102
- fn parse_label_values(json: &str) -> Option<MetricLabelVals> {
103
- // Starting with: ["label_a","label_b"],["value_a", "value_b"]]
104
- if !json.starts_with('[') {
105
- return None;
106
- }
107
-
108
- // Now: "label_a","label_b"],["value_a", "value_b"]]
109
- let mut remainder = json.get(1..)?;
110
-
111
- // Validate we either have the start of a label string or an
112
- // empty array, e.g. `["` or `[]`.
113
- if !matches!(json.as_bytes().get(1)?, b'"' | b']') {
114
- return None;
115
- }
116
-
117
- let mut labels = SmallVec::new();
118
-
119
- // Split on commas into:
120
- // "label_a","label_b"
121
- // ^^^one^^^ ^^^two^^^
122
- loop {
123
- let Some((label, label_rem)) = scan_str(remainder) else {
124
- // No further keys.
125
- break;
126
- };
127
- labels.push(label);
128
-
129
- // Advance past trailing quote.
130
- remainder = label_rem.get(1..)?;
131
- match remainder.as_bytes().first() {
132
- Some(b']') => break, // No further labels.
133
- Some(b',') => {} // More labels expected.
134
- _ => return None, // Invalid.
135
- };
136
-
137
- // Advance past comma.
138
- remainder = remainder.get(1..)?;
139
- }
140
-
141
- if !remainder.starts_with("],[") {
142
- return None;
143
- }
144
- // Now: "value_a", "value_b"]]
145
- remainder = remainder.get(3..)?;
146
-
147
- // Validate we don't have extra brackets.
148
- if remainder.starts_with('[') {
149
- return None;
150
- }
151
-
152
- let mut values = SmallVec::new();
153
- // Split on commas into:
154
- // "value_a","value_b"
155
- // ^^^one^^^ ^^^two^^^
156
- loop {
157
- if remainder.starts_with('"') {
158
- let (value, value_rem) = scan_str(remainder)?;
159
- values.push(value);
160
-
161
- // Advance past trailing quote.
162
- remainder = value_rem.get(1..)?;
163
- } else {
164
- // An unquoted value, such as `true` or `404`.
165
- let i = remainder.find(|c| c == ',' || c == ']')?;
166
- let value = &remainder[..i];
167
-
168
- match (value.is_empty(), is_valid_json_literal(value)) {
169
- (true, _) => {} // Empty string, do nothing.
170
- (false, true) => values.push(value), // Valid literal.
171
- (false, false) => return None, // Invalid literal, fail.
172
- }
173
-
174
- remainder = &remainder[i..];
175
- }
176
-
177
- match remainder.as_bytes().first() {
178
- Some(b']') => break, // End of values.
179
- Some(b',') => {} // More values expected.
180
- _ => return None, // Invalid.
181
- };
182
-
183
- // Advance past comma.
184
- remainder = remainder.get(1..)?;
185
- }
186
-
187
- if values.len() != labels.len() {
188
- return None;
189
- }
190
-
191
- // Now: ]]
192
- if remainder != "]]" {
193
- return None;
194
- }
195
-
196
- Some(MetricLabelVals { labels, values })
197
- }
198
-
199
- // Read a JSON-encoded str that includes starting and ending double quotes.
200
- // Returns the validated str with the double quotes trimmed and the remainder
201
- // of the input str.
202
- fn scan_str(json: &str) -> Option<(&str, &str)> {
203
- let mut escaping = false;
204
-
205
- if !json.starts_with('"') {
206
- return None;
207
- }
208
-
209
- // Trim leading double quote.
210
- let json = json.get(1..)?;
211
-
212
- for (i, &c) in json.as_bytes().iter().enumerate() {
213
- if c == b'\\' {
214
- escaping = true;
215
- continue;
216
- }
217
-
218
- if c == b'"' && !escaping {
219
- return Some((json.get(..i)?, json.get(i..)?));
220
- }
221
-
222
- escaping = false;
223
- }
224
-
225
- None
226
- }
227
-
228
- // Confirm an unquoted JSON literal is a boolean, null, or has a passing
229
- // resemblance to a number. We do not confirm numeric formatting, only
230
- // that all characters are valid. See https://www.json.org/json-en.html
231
- // for details.
232
- fn is_valid_json_literal(s: &str) -> bool {
233
- match s {
234
- "true" | "false" | "null" => true,
235
- _ => s.chars().all(|c| {
236
- c.is_ascii_digit() || c == '.' || c == '+' || c == '-' || c == 'e' || c == 'E'
237
- }),
238
- }
239
- }
240
-
241
- #[cfg(test)]
242
- mod test {
243
- use smallvec::smallvec;
244
-
245
- use super::*;
246
-
247
- struct TestCase {
248
- name: &'static str,
249
- input: &'static str,
250
- expected: Option<MetricText<'static>>,
251
- }
252
-
253
- #[test]
254
- fn valid_json() {
255
- let tc = vec![
256
- TestCase {
257
- name: "basic",
258
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
259
- expected: Some(MetricText {
260
- family_name: "metric",
261
- metric_name: "name",
262
- labels: smallvec!["label_a", "label_b"],
263
- values: smallvec!["value_a", "value_b"],
264
- }),
265
- },
266
- TestCase {
267
- name: "many labels",
268
- input: r#"["metric","name",["label_a","label_b","label_c","label_d","label_e"],["value_a","value_b","value_c","value_d","value_e"]]"#,
269
-
270
- expected: Some(MetricText {
271
- family_name: "metric",
272
- metric_name: "name",
273
- labels: smallvec!["label_a", "label_b", "label_c", "label_d", "label_e"],
274
- values: smallvec!["value_a", "value_b", "value_c", "value_d", "value_e"],
275
- }),
276
- },
277
- TestCase {
278
- name: "numeric value",
279
- input: r#"["metric","name",["label_a","label_b"],["value_a",403]]"#,
280
- expected: Some(MetricText {
281
- family_name: "metric",
282
- metric_name: "name",
283
- labels: smallvec!["label_a", "label_b"],
284
- values: smallvec!["value_a", "403"],
285
- }),
286
- },
287
- TestCase {
288
- name: "scientific notation literal",
289
- input: r#"["metric","name",["label_a"],[-2.0e-5]]"#,
290
- expected: Some(MetricText {
291
- family_name: "metric",
292
- metric_name: "name",
293
- labels: smallvec!["label_a"],
294
- values: smallvec!["-2.0e-5"],
295
- }),
296
- },
297
- TestCase {
298
- name: "null value",
299
- input: r#"["metric","name",["label_a","label_b"],[null,"value_b"]]"#,
300
- expected: Some(MetricText {
301
- family_name: "metric",
302
- metric_name: "name",
303
- labels: smallvec!["label_a", "label_b"],
304
- values: smallvec!["null", "value_b"],
305
- }),
306
- },
307
- TestCase {
308
- name: "no labels",
309
- input: r#"["metric","name",[],[]]"#,
310
- expected: Some(MetricText {
311
- family_name: "metric",
312
- metric_name: "name",
313
- labels: smallvec![],
314
- values: smallvec![],
315
- }),
316
- },
317
- TestCase {
318
- name: "comma in items",
319
- input: r#"["met,ric","na,me",["label,_a","label_b"],["value,_a","value_b"]]"#,
320
- expected: Some(MetricText {
321
- family_name: "met,ric",
322
- metric_name: "na,me",
323
- labels: smallvec!["label,_a", "label_b"],
324
- values: smallvec!["value,_a", "value_b"],
325
- }),
326
- },
327
- TestCase {
328
- name: "bracket in value",
329
- input: r#"["met[r]ic","na[m]e",["label[_]a","label_b"],["value_a","val[ue]_b"]]"#,
330
- expected: Some(MetricText {
331
- family_name: "met[r]ic",
332
- metric_name: "na[m]e",
333
- labels: smallvec!["label[_]a", "label_b"],
334
- values: smallvec!["value_a", "val[ue]_b"],
335
- }),
336
- },
337
- TestCase {
338
- name: "escaped quotes",
339
- input: r#"["met\"ric","na\"me",["label\"_a","label_b"],["value\"_a","value_b"]]"#,
340
- expected: Some(MetricText {
341
- family_name: r#"met\"ric"#,
342
- metric_name: r#"na\"me"#,
343
- labels: smallvec![r#"label\"_a"#, "label_b"],
344
- values: smallvec![r#"value\"_a"#, "value_b"],
345
- }),
346
- },
347
- ];
348
-
349
- for case in tc {
350
- assert_eq!(
351
- parse_metrics(case.input),
352
- case.expected,
353
- "test case: {}",
354
- case.name,
355
- );
356
- }
357
- }
358
-
359
- #[test]
360
- fn invalid_json() {
361
- let tc = vec![
362
- TestCase {
363
- name: "not json",
364
- input: "hello, world",
365
- expected: None,
366
- },
367
- TestCase {
368
- name: "no names",
369
- input: r#"[["label_a","label_b"],["value_a","value_b"]]"#,
370
- expected: None,
371
- },
372
- TestCase {
373
- name: "too many names",
374
- input: r#"["metric","name","unexpected_name",["label_a","label_b"],["value_a","value_b"]]"#,
375
- expected: None,
376
- },
377
- TestCase {
378
- name: "too many labels",
379
- input: r#"["metric","name","unexpected_name",["label_a","label_b","label_c"],["value_a","value_b"]]"#,
380
- expected: None,
381
- },
382
- TestCase {
383
- name: "too many values",
384
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b",null]]"#,
385
- expected: None,
386
- },
387
- TestCase {
388
- name: "no values",
389
- input: r#"["metric","name",["label_a","label_b"]"#,
390
- expected: None,
391
- },
392
- TestCase {
393
- name: "no arrays",
394
- input: r#"["metric","name","label_a","value_a"]"#,
395
- expected: None,
396
- },
397
- TestCase {
398
- name: "too many leading brackets",
399
- input: r#"[["metric","name",["label_a","label_b"],["value_a","value_b"]]"#,
400
- expected: None,
401
- },
402
- TestCase {
403
- name: "too many trailing brackets",
404
- input: r#"["metric","name",["label_a","label_b"],["value_a","value_b"]]]"#,
405
- expected: None,
406
- },
407
- TestCase {
408
- name: "too many leading label brackets",
409
- input: r#"["metric","name",[["label_a","label_b"],["value_a","value_b"]]"#,
410
- expected: None,
411
- },
412
- TestCase {
413
- name: "too many trailing label brackets",
414
- input: r#"["metric","name",["label_a","label_b"]],["value_a","value_b"]]"#,
415
- expected: None,
416
- },
417
- TestCase {
418
- name: "too many leading value brackets",
419
- input: r#"["metric","name",["label_a","label_b"],[["value_a","value_b"]]"#,
420
- expected: None,
421
- },
422
- TestCase {
423
- name: "misplaced bracket",
424
- input: r#"["metric","name",["label_a","label_b"],]["value_a","value_b"]]"#,
425
- expected: None,
426
- },
427
- TestCase {
428
- name: "comma in numeric value",
429
- input: r#"["metric","name",["label_a","label_b"],[400,0,"value_b"]]"#,
430
- expected: None,
431
- },
432
- TestCase {
433
- name: "non-e letter in numeric value",
434
- input: r#"["metric","name",["label_a","label_b"],[400x0,"value_b"]]"#,
435
- expected: None,
436
- },
437
- ];
438
-
439
- for case in tc {
440
- assert_eq!(
441
- case.expected,
442
- parse_metrics(case.input),
443
- "test case: {}",
444
- case.name,
445
- );
446
- }
447
- }
448
- }