topcoat-view 0.9.0

A modular, batteries-included Rust web framework for server-rendered apps.
Documentation
use std::{
    future::Ready,
    pin::Pin,
    task::{Context, Poll},
};

use pin_project_lite::pin_project;
use topcoat_core::error::Result;

use super::yielder::DriveFuture;
use crate::{
    View, ViewFirst, ViewSwap,
    internal::yielder::{poll_first, poll_swap},
};

pin_project! {
    /// A [`View`] polled through an async body that owns data the view
    /// borrows.
    ///
    /// The async body owns the captured values and drives the nested view
    /// while its borrows remain valid. Resolved content passes to the
    /// enclosing poll through the yielder, one value per poll.
    pub struct MoveView<Fut> {
        #[pin]
        body: Fut,
    }
}

impl<Fut> MoveView<Fut>
where
    Fut: Future<Output = Result<()>>,
{
    #[doc(hidden)]
    pub fn new(body: Fut) -> Self {
        Self { body }
    }
}

impl MoveView<Ready<()>> {
    /// Drives `view` inside a move body, handing its first content and
    /// every swap after it to the enclosing poll; resolves once the view
    /// has no further updates.
    pub fn drive<V: View>(view: V) -> impl Future<Output = Result<()>> {
        DriveFuture::new(view)
    }
}

impl<Fut> View for MoveView<Fut>
where
    Fut: Future<Output = Result<()>> + Send,
{
    fn poll_first(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<ViewFirst>> {
        let this = self.project();

        match poll_first(this.body, cx) {
            (Poll::Pending, Some(first)) => Poll::Ready(Ok(first)),
            (Poll::Pending, None) => Poll::Pending,
            (Poll::Ready(_), Some(_)) => {
                panic!("move view future yielded without returning pending")
            }
            (Poll::Ready(Err(e)), None) => Poll::Ready(Err(e)),
            (Poll::Ready(Ok(())), None) => {
                panic!("move view future completed without yielding anything")
            }
        }
    }

    fn poll_swap(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<Option<ViewSwap>>> {
        let this = self.project();

        match poll_swap(this.body, cx) {
            (Poll::Pending, Some(swap)) => Poll::Ready(Ok(Some(swap))),
            (Poll::Pending, None) => Poll::Pending,
            (Poll::Ready(_), Some(_)) => {
                panic!("move view future yielded without returning pending")
            }
            (Poll::Ready(Err(e)), None) => Poll::Ready(Err(e)),
            (Poll::Ready(Ok(())), None) => Poll::Ready(Ok(None)),
        }
    }
}