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§

source

type Error: Error + ErrIntoHttpResponse + Send

The error type that can be returned by individual responses.

source

type InitialResponse: IntoHttpResponse + Send

The type returned as the initial response returned back to Grafana.

This must be convertable into a http::Response<Bytes>.

source

type Stream: Stream<Item = Result<Bytes, Self::Error>> + Send

The type of stream of optional additional data returned by run_stream.

This will generally be impossible to name directly, so returning the BoxResourceStream type alias will probably be more convenient.

Required Methods§

source

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.

Implementors§

source§

impl<T, F, R, E> ResourceService for T
where T: Fn(CallResourceRequest) -> F + Sync, F: Future<Output = Result<R, E>> + Send, R: IntoHttpResponse + Send + Sync, E: Error + ErrIntoHttpResponse + Send + Sync,

§

type Error = E

§

type InitialResponse = R

§

type Stream = Empty<Result<Bytes, <T as ResourceService>::Error>>