use occult::{Extractor, Handler, State};
#[derive(Debug, Clone, PartialEq)]
struct Frame(Vec<u8>);
impl From<Vec<u8>> for Frame {
fn from(vec: Vec<u8>) -> Self {
Frame(vec)
}
}
impl From<String> for Frame {
fn from(string: String) -> Self {
Frame(string.into_bytes())
}
}
impl std::str::FromStr for Frame {
type Err = std::string::FromUtf8Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Frame(s.as_bytes().to_vec()))
}
}
#[derive(Debug, Clone)]
struct Topic(String);
impl std::fmt::Display for Topic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl<State> Extractor<Frame, State> for Topic {
type Error = String;
fn extract<Context>(topic: Frame, _: &Context) -> Result<Self, Self::Error>
where
Self: Sized,
{
let topic = String::from_utf8(topic.0).map_err(|err| err.to_string())?;
Ok(Topic(topic))
}
}
#[tokio::test]
async fn simple_extract() -> Result<(), Box<dyn std::error::Error>> {
async fn handler(topic: Topic) -> String {
format!("Hello, {topic}!")
}
assert_eq!(
handler.invoke(Frame(b"world".to_vec()), ()).await?,
Frame(b"Hello, world!".to_vec())
);
Ok(())
}
#[tokio::test]
async fn async_closures() -> Result<(), Box<dyn std::error::Error>> {
let handler = |topic: Topic| async move { format!("Hello, {topic}!") };
assert_eq!(
handler.invoke(Frame(b"world".to_vec()), ()).await?,
Frame(b"Hello, world!".to_vec())
);
Ok(())
}
#[tokio::test]
async fn coerced_types() -> Result<(), Box<dyn std::error::Error>> {
async fn handler(topic: Topic) -> String {
format!("Hello, {topic}!")
}
assert_eq!(
handler.invoke("world".to_string(), ()).await?,
Frame(b"Hello, world!".to_vec())
);
assert_eq!(
handler.invoke(b"world".to_vec(), ()).await?,
Frame(b"Hello, world!".to_vec())
);
Ok(())
}
#[tokio::test]
async fn coerced_types_closures() -> Result<(), Box<dyn std::error::Error>> {
let handler = |topic: Topic| async move { format!("Hello, {topic}!") };
assert_eq!(
handler.invoke("world".to_string(), ()).await?,
Frame(b"Hello, world!".to_vec())
);
assert_eq!(
handler.invoke(b"world".to_vec(), ()).await?,
Frame(b"Hello, world!".to_vec())
);
Ok(())
}
#[tokio::test]
async fn multiple_args() -> Result<(), Box<dyn std::error::Error>> {
async fn multi_handler(topic: Topic, topic2: Topic, topic3: Topic) -> String {
format!("Hello, {topic} {topic2} {topic3}!")
}
assert_eq!(
multi_handler.invoke(Frame(b"world".to_vec()), ()).await?,
Frame(b"Hello, world world world!".to_vec())
);
Ok(())
}
#[tokio::test]
async fn narrowing_types() -> Result<(), Box<dyn std::error::Error>> {
async fn handle_the_handler<Args, State>(
handler: impl Handler<Frame, Args, State, Error = Box<dyn std::error::Error>>,
state: State,
) -> Result<Frame, Box<dyn std::error::Error>> {
match handler.invoke(Frame(b"world".to_vec()), state).await {
Ok(frame) => Ok(frame.into()),
Err(err) => Err(err),
}
}
async fn tersely_handle_the_handler<Args, State>(
handler: impl Handler<Frame, Args, State, Error = Box<dyn std::error::Error>>,
state: State,
) -> Result<Frame, Box<dyn std::error::Error>> {
handler
.invoke(Frame(b"world".to_vec()), state)
.await
.map(Into::into)
}
async fn handler(topic: Topic) -> String {
format!("Hello, {topic}!")
}
assert_eq!(
handle_the_handler(&handler, ()).await?,
Frame(b"Hello, world!".to_vec())
);
assert_eq!(
tersely_handle_the_handler(&handler, ()).await?,
Frame(b"Hello, world!".to_vec())
);
Ok(())
}
#[tokio::test]
async fn narrowing_types_closures() -> Result<(), Box<dyn std::error::Error>> {
let handler = |topic: Topic| async move { format!("Hello, {topic}!") };
async fn tersely_handle_the_handler<Args, State>(
handler: impl Handler<Frame, Args, State, Error = Box<dyn std::error::Error>>,
state: State,
) -> Result<Frame, Box<dyn std::error::Error>> {
handler
.invoke(Frame(b"world".to_vec()), state)
.await
.map(Into::into)
}
assert_eq!(
tersely_handle_the_handler(handler, ()).await?,
Frame(b"Hello, world!".to_vec())
);
Ok(())
}
#[tokio::test]
async fn extracting_from_state() -> Result<(), Box<dyn std::error::Error>> {
#[derive(Debug, Clone)]
struct ArbitraryState;
impl ArbitraryState {
fn how_arbitrary(&self) -> &'static str {
"very arbitrary"
}
}
async fn handler(topic: Topic, State(state): State<ArbitraryState>) -> String {
format!(
"Hello, {topic} - {how_arbitrary}!",
how_arbitrary = state.how_arbitrary()
)
}
assert_eq!(
handler
.invoke(Frame(b"world".to_vec()), ArbitraryState)
.await?,
Frame(b"Hello, world - very arbitrary!".to_vec())
);
Ok(())
}