1mod label;
15mod metric_descriptor;
16mod metric_name;
17mod number;
18mod string;
19
20use crate::{Family, Sample};
21use label::labels;
22use metric_descriptor::metric_descriptor;
23use metric_name::metric_name;
24use nom::{
25 bytes::complete::tag,
26 character::complete::char,
27 combinator::{all_consuming, cut, eof, map, opt},
28 error::context,
29 multi::{many0, many1},
30 sequence::{pair, preceded, terminated},
31 IResult, Parser,
32};
33use nom_language::error::VerboseError;
34use number::number;
35
36pub fn eof_marker(input: &str) -> IResult<&str, (), VerboseError<&str>> {
38 context("eof", map((tag("# EOF"), opt(char('\n')), eof), |_| ())).parse(input)
39}
40
41pub fn openmetrics(input: &str) -> IResult<&str, Vec<Family>, VerboseError<&str>> {
45 context("openmetrics", terminated(set, eof_marker)).parse(input)
46}
47
48pub fn family(input: &str) -> IResult<&str, Family, VerboseError<&str>> {
50 context(
51 "family",
52 map(
53 pair(many0(metric_descriptor), many1(sample)),
54 |(descriptors, samples)| Family::new(descriptors, samples),
55 ),
56 )
57 .parse(input)
58}
59
60pub fn prometheus(input: &str) -> IResult<&str, Vec<Family>, VerboseError<&str>> {
64 context("prometheus", all_consuming(terminated(set, cut(eof)))).parse(input)
65}
66
67pub(crate) fn sample(input: &str) -> IResult<&str, Sample, VerboseError<&str>> {
69 context(
70 "sample",
71 map(
72 terminated(
73 (metric_name, opt(labels), preceded(char(' '), metric_value)),
74 char('\n'),
75 ),
76 |(name, labels, number)| {
77 if let Some(labels) = labels {
78 Sample::with_labels(name, number, labels)
79 } else {
80 Sample::new(name, number)
81 }
82 },
83 ),
84 )
85 .parse(input)
86}
87
88fn set(input: &str) -> IResult<&str, Vec<Family>, VerboseError<&str>> {
89 context("set", many0(family)).parse(input)
90}
91
92fn metric_value(input: &str) -> IResult<&str, f64, VerboseError<&str>> {
94 context("metric value", number).parse(input)
95}
96
97#[cfg(test)]
98mod test {
99 use super::*;
100 use crate::{test::parse, MetricDescriptor, MetricType};
101 use rstest::rstest;
102
103 #[rstest]
104 #[case("# EOF")]
105 #[case("# EOF\n")]
106 fn eof_marker(#[case] input: &str) {
107 let (rest, _) = parse(super::eof_marker, input);
108
109 assert!(rest.is_empty(), "leftover: {rest:?}");
110 }
111
112 #[test]
113 fn openmetrics() {
114 let input = "# HELP up up help text\nup{job=\"prometheus\"} 1\n# EOF\n";
115
116 let (rest, openmetrics) = parse(super::openmetrics, input);
117
118 assert_eq!(
119 "up",
120 openmetrics
121 .first()
122 .expect("parsed one family")
123 .descriptors
124 .first()
125 .unwrap()
126 .metric()
127 );
128
129 assert!(rest.is_empty(), "leftover: {rest:?}");
130 }
131
132 #[test]
133 fn family() {
134 let input = "# TYPE up gauge\n# HELP up up help text\nup{job=\"prometheus\"} 1\nup{job=\"grafana\"} 0\n";
135
136 let (rest, family) = parse(super::family, input);
137
138 assert_eq!(
139 MetricDescriptor::r#type("up", MetricType::Gauge),
140 family.descriptors[0]
141 );
142
143 assert_eq!(
144 MetricDescriptor::help("up", "up help text".into()),
145 family.descriptors[1]
146 );
147
148 assert_eq!(
149 Sample::new("up", 1.0).add_label("job", "prometheus"),
150 family.samples[0]
151 );
152
153 assert_eq!(
154 Sample::new("up", 0.0).add_label("job", "grafana"),
155 family.samples[1]
156 );
157
158 assert!(rest.is_empty(), "leftover: {rest:?}");
159 }
160
161 #[rstest]
162 #[case("up 1\n", Sample::new("up", 1.0))]
163 #[case("up{job=\"prometheus\"} 2\n", Sample::new("up", 2.0).add_label("job", "prometheus"))]
164 #[case("up{job=\"☃\"} 1\n", Sample::new("up", 1.0).add_label("job", "☃"))]
165 fn sample(#[case] input: &str, #[case] expected: Sample<'_>) {
166 let (rest, metric) = parse(super::sample, input);
167
168 assert_eq!(expected, metric, "input: {input} metric: {metric:?}");
169 assert!(rest.is_empty());
170 }
171
172 #[test]
173 fn prometheus() {
174 let input = "# HELP up up help text\nup{job=\"prometheus\"} 1\n";
175
176 let (rest, prometheus) = parse(super::prometheus, input);
177
178 assert!(rest.is_empty(), "leftover: {rest:?}");
179
180 assert_eq!(
181 "up",
182 prometheus
183 .first()
184 .expect("parsed one family")
185 .descriptors
186 .first()
187 .unwrap()
188 .metric()
189 );
190 }
191}