Trait grafana_plugin_sdk::backend::ResourceService
source · pub trait ResourceService {
type Error: Error + ErrIntoHttpResponse + Send;
type InitialResponse: IntoHttpResponse + Send;
type Stream: Stream<Item = Result<Bytes, Self::Error>> + Send;
// Required method
fn call_resource<'life0, 'async_trait>(
&'life0 self,
request: CallResourceRequest
) -> Pin<Box<dyn Future<Output = Result<(Self::InitialResponse, Self::Stream), Self::Error>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait;
}
Expand description
Trait for plugins that can handle arbitrary resource requests.
Implementing this trait allows plugins to handle a wide variety of use cases beyond ‘just’ responding to requests for data and returning dataframes.
See https://grafana.com/docs/grafana/latest/developers/plugins/backend/#resources for some examples of how this can be used.
Examples
Simple function
This trait has a blanket impl for async functions taking a CallResourceRequest
and
returning a Result<T, E> where T: IntoHttpResponse + Send, E: std::error::Error + Send
.
IntoHttpResponse
is implemented for some types already - see its docs for details.
Note that the reqwest
feature of this crate is required for the IntoHttpResponse
implementation to be enabled for reqwest::Response
.
The example below requires the reqwest
feature:
use bytes::Bytes;
use grafana_plugin_sdk::{backend, prelude::*};
use reqwest::{Error, Response};
async fn respond(req: backend::CallResourceRequest) -> Result<Response, Error> {
reqwest::get("https://www.rust-lang.org").await
}
let plugin = backend::Plugin::new().resource_service(respond);
Stateful service
The following shows an example implementation of ResourceService
which handles
two endpoints:
- /echo, which echos back the request’s URL, headers and body in three responses,
- /count, which increments the plugin’s internal count and returns it in a response.
use std::sync::{atomic::{AtomicUsize, Ordering}, Arc};
use async_stream::stream;
use bytes::Bytes;
use grafana_plugin_sdk::backend;
use http::Response;
use thiserror::Error;
struct Plugin(Arc<AtomicUsize>);
impl Plugin {
// Increment the counter and return the stringified result in a `Response`.
fn inc_and_respond(&self) -> Response<Bytes> {
Response::new(
self.0
.fetch_add(1, Ordering::SeqCst)
.to_string()
.into_bytes()
.into()
)
}
}
#[derive(Debug, Error)]
enum ResourceError {
#[error("HTTP error: {0}")]
Http(#[from] http::Error),
#[error("Path not found")]
NotFound,
}
impl backend::ErrIntoHttpResponse for ResourceError {}
#[backend::async_trait]
impl backend::ResourceService for Plugin {
type Error = ResourceError;
type InitialResponse = Response<Bytes>;
type Stream = backend::BoxResourceStream<Self::Error>;
async fn call_resource(&self, r: backend::CallResourceRequest) -> Result<(Self::InitialResponse, Self::Stream), Self::Error> {
let count = Arc::clone(&self.0);
let response_and_stream = match r.request.uri().path() {
// Just send back a single response.
"/echo" => Ok((
Response::new(r.request.into_body().into()),
Box::pin(futures::stream::empty()) as Self::Stream,
)),
// Send an initial response with the current count, then stream the gradually
// incrementing count back to the client.
"/count" => Ok((
Response::new(
count
.fetch_add(1, Ordering::SeqCst)
.to_string()
.into_bytes()
.into(),
),
Box::pin(async_stream::try_stream! {
loop {
let body = count
.fetch_add(1, Ordering::SeqCst)
.to_string()
.into_bytes()
.into();
yield body;
}
}) as Self::Stream,
)),
_ => return Err(ResourceError::NotFound),
};
response_and_stream
}
}
Required Associated Types§
sourcetype Error: Error + ErrIntoHttpResponse + Send
type Error: Error + ErrIntoHttpResponse + Send
The error type that can be returned by individual responses.
sourcetype InitialResponse: IntoHttpResponse + Send
type InitialResponse: IntoHttpResponse + Send
The type returned as the initial response returned back to Grafana.
This must be convertable into a http::Response<Bytes>
.
Required Methods§
sourcefn call_resource<'life0, 'async_trait>(
&'life0 self,
request: CallResourceRequest
) -> Pin<Box<dyn Future<Output = Result<(Self::InitialResponse, Self::Stream), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
fn call_resource<'life0, 'async_trait>(
&'life0 self,
request: CallResourceRequest
) -> Pin<Box<dyn Future<Output = Result<(Self::InitialResponse, Self::Stream), Self::Error>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
Handle a resource request.
It is completely up to the implementor how to handle the incoming request.
A stream of responses can be returned. A simple way to return just a single response
is to use futures_util::stream::once
.