1use crate::{
10 method::ReadState,
11 request::RequestParams,
12 route::{self, RouteError},
13};
14use async_trait::async_trait;
15use derive_more::From;
16use futures::future::{BoxFuture, FutureExt};
17use prometheus::{Encoder, TextEncoder};
18use std::{borrow::Cow, error::Error, fmt::Debug};
19
20pub trait Metrics {
21 type Error: Debug + Error;
22
23 fn export(&self) -> Result<String, Self::Error>;
24}
25
26impl Metrics for prometheus::Registry {
27 type Error = prometheus::Error;
28
29 fn export(&self) -> Result<String, Self::Error> {
30 let mut buffer = vec![];
31 let encoder = TextEncoder::new();
32 let metric_families = self.gather();
33 encoder.encode(&metric_families, &mut buffer)?;
34 String::from_utf8(buffer).map_err(|err| {
35 prometheus::Error::Msg(format!(
36 "could not convert Prometheus output to UTF-8: {err}",
37 ))
38 })
39 }
40}
41
42#[derive(From)]
54pub(crate) struct Handler<F>(F);
55
56#[async_trait]
57impl<F, T, State, Error> route::Handler<State, Error> for Handler<F>
58where
59 F: 'static + Send + Sync + Fn(RequestParams, &State::State) -> BoxFuture<Result<Cow<T>, Error>>,
60 T: 'static + Clone + Metrics,
61 State: 'static + Send + Sync + ReadState,
62 Error: 'static,
63{
64 async fn handle(
65 &self,
66 req: RequestParams,
67 state: &State,
68 ) -> Result<tide::Response, RouteError<Error>> {
69 let exported = state
70 .read(|state| {
71 let fut = (self.0)(req, state);
72 async move {
73 let metrics = fut.await.map_err(RouteError::AppSpecific)?;
74 metrics
75 .export()
76 .map_err(|err| RouteError::ExportMetrics(err.to_string()))
77 }
78 .boxed()
79 })
80 .await?;
81 Ok(exported.into())
82 }
83}