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}