use crate::{Answer, Error, ErrorKind, IndexMap, Question, Result, State};
use std::{future::Future, pin::Pin, sync::Arc};
pub type Questions = IndexMap<String, Question>;
pub type Answers = IndexMap<String, Answer>;
pub type ProviderFuture<'a> = Pin<Box<dyn Future<Output = Result<Answers>> + Send + 'a>>;
#[derive(Clone)]
pub struct Request {
state: State,
questions: Questions,
#[cfg(feature = "images")]
assets: crate::image::Assets,
}
impl std::fmt::Debug for Request {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Request")
.field("question_count", &self.questions.len())
.finish_non_exhaustive()
}
}
impl Request {
pub fn new(state: State, questions: Questions) -> Result<Self> {
if questions.is_empty() || questions.keys().any(String::is_empty) {
return Err(Error::new(
ErrorKind::InvalidArgument,
"a request needs nonempty, uniquely named questions",
));
}
Ok(Self {
state,
questions,
#[cfg(feature = "images")]
assets: Default::default(),
})
}
pub fn state(&self) -> &State {
&self.state
}
pub fn questions(&self) -> &Questions {
&self.questions
}
pub fn with_questions(&self, questions: Questions) -> Result<Self> {
let request = Self::new(self.state.clone(), questions)?;
#[cfg(feature = "images")]
let request = request.with_assets(self.assets.clone());
Ok(request)
}
#[cfg(feature = "images")]
#[cfg_attr(docsrs, doc(cfg(feature = "images")))]
pub fn with_assets(mut self, assets: crate::image::Assets) -> Self {
self.assets = assets;
self
}
#[cfg(feature = "images")]
#[cfg_attr(docsrs, doc(cfg(feature = "images")))]
pub fn assets(&self) -> &crate::image::Assets {
&self.assets
}
pub fn validate(&self) -> Result<()> {
#[cfg(feature = "images")]
self.assets.validate(&self.state, &self.questions)?;
Ok(())
}
}
pub trait Provider: Send + Sync {
fn ask<'a>(&'a self, request: &'a Request) -> ProviderFuture<'a>;
}
impl<P: Provider + ?Sized> Provider for Arc<P> {
fn ask<'a>(&'a self, request: &'a Request) -> ProviderFuture<'a> {
(**self).ask(request)
}
}
pub async fn ask<P: Provider + ?Sized>(provider: &P, request: &Request) -> Result<Answers> {
request.validate()?;
validate_answers(request, provider.ask(request).await?)
}
pub fn validate_answers(request: &Request, answers: Answers) -> Result<Answers> {
if answers.len() != request.questions.len()
|| answers
.keys()
.any(|name| !request.questions.contains_key(name))
{
return Err(Error::new(
ErrorKind::InvalidResponse,
"provider must answer exactly the requested question names",
));
}
request
.questions
.iter()
.map(|(name, question)| {
let answer = answers.get(name).ok_or_else(|| {
Error::new(
ErrorKind::InvalidResponse,
"provider omitted a requested answer",
)
})?;
Ok((name.clone(), answer.revalidate(question)?))
})
.collect()
}
pub struct FunctionProvider<F> {
function: F,
}
impl<F> FunctionProvider<F>
where
F: for<'a> Fn(&'a Request) -> ProviderFuture<'a> + Send + Sync,
{
pub fn new(function: F) -> Self {
Self { function }
}
}
impl<F> Provider for FunctionProvider<F>
where
F: for<'a> Fn(&'a Request) -> ProviderFuture<'a> + Send + Sync,
{
fn ask<'a>(&'a self, request: &'a Request) -> ProviderFuture<'a> {
Box::pin(async move { (self.function)(request).await })
}
}
#[cfg(feature = "tokio")]
pub(crate) async fn deadline<F: Future>(
duration: std::time::Duration,
future: F,
) -> Result<F::Output> {
if tokio::runtime::Handle::try_current().is_err() {
return Err(Error::new(
ErrorKind::Runtime,
"deadline requires a time-enabled Tokio runtime",
));
}
let timer = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
tokio::time::timeout(duration, future)
}))
.map_err(|_| Error::new(ErrorKind::Runtime, "Tokio timer driver is not enabled"))?;
timer
.await
.map_err(|_| Error::new(ErrorKind::Timeout, "provider call exceeded its deadline"))
}