leyline-http 0.1.1

HTTP client that mimics browsers on the wire
Documentation
use crate::h2::error::ErrorCode;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClosedReason {
    EndStream,
    RstRemote(ErrorCode),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamState {
    Idle,
    Open,
    HalfClosedLocal,
    HalfClosedRemote,
    Closed { reason: ClosedReason },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StreamEvent {
    SendHeaders { end_stream: bool },
    SendData { end_stream: bool },
    RecvHeaders { end_stream: bool },
    RecvData { end_stream: bool },
    RecvTrailers,
    RecvRstStream(ErrorCode),
}

#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum StreamStateError {
    #[error("invalid transition: cannot apply {event} in state {from}")]
    InvalidTransition {
        from: &'static str,
        event: &'static str,
    },
}

impl StreamState {
    pub fn transition(&mut self, event: StreamEvent) -> Result<(), StreamStateError> {
        let next = next_state(*self, event)?;
        *self = next;
        Ok(())
    }

    pub fn is_closed(&self) -> bool {
        matches!(self, StreamState::Closed { .. })
    }
}

fn state_name(state: StreamState) -> &'static str {
    match state {
        StreamState::Idle => "Idle",
        StreamState::Open => "Open",
        StreamState::HalfClosedLocal => "HalfClosedLocal",
        StreamState::HalfClosedRemote => "HalfClosedRemote",
        StreamState::Closed { .. } => "Closed",
    }
}

fn event_name(event: StreamEvent) -> &'static str {
    match event {
        StreamEvent::SendHeaders { .. } => "SendHeaders",
        StreamEvent::SendData { .. } => "SendData",
        StreamEvent::RecvHeaders { .. } => "RecvHeaders",
        StreamEvent::RecvData { .. } => "RecvData",
        StreamEvent::RecvTrailers => "RecvTrailers",
        StreamEvent::RecvRstStream(_) => "RecvRstStream",
    }
}

fn invalid(from: StreamState, event: StreamEvent) -> StreamStateError {
    StreamStateError::InvalidTransition {
        from: state_name(from),
        event: event_name(event),
    }
}

fn next_state(from: StreamState, event: StreamEvent) -> Result<StreamState, StreamStateError> {
    use StreamEvent as E;
    use StreamState as S;

    match (from, event) {
        (S::Idle, E::RecvRstStream(_)) => {
            return Err(invalid(from, event));
        }
        (_, E::RecvRstStream(code)) => {
            return Ok(S::Closed {
                reason: ClosedReason::RstRemote(code),
            });
        }
        _ => {}
    }

    match from {
        S::Idle => match event {
            E::SendHeaders { end_stream: false } => Ok(S::Open),
            E::SendHeaders { end_stream: true } => Ok(S::HalfClosedLocal),
            _ => Err(invalid(from, event)),
        },
        S::Open => match event {
            E::SendData { end_stream: false } => Ok(S::Open),
            E::SendData { end_stream: true } => Ok(S::HalfClosedLocal),
            E::RecvHeaders { end_stream: false } => Ok(S::Open),
            E::RecvHeaders { end_stream: true } => Ok(S::HalfClosedRemote),
            E::RecvData { end_stream: false } => Ok(S::Open),
            E::RecvData { end_stream: true } => Ok(S::HalfClosedRemote),
            E::RecvTrailers => Ok(S::HalfClosedRemote),
            _ => Err(invalid(from, event)),
        },
        S::HalfClosedLocal => match event {
            E::RecvHeaders { end_stream: false } => Ok(S::HalfClosedLocal),
            E::RecvHeaders { end_stream: true } => Ok(S::Closed {
                reason: ClosedReason::EndStream,
            }),
            E::RecvData { end_stream: false } => Ok(S::HalfClosedLocal),
            E::RecvData { end_stream: true } => Ok(S::Closed {
                reason: ClosedReason::EndStream,
            }),
            E::RecvTrailers => Ok(S::Closed {
                reason: ClosedReason::EndStream,
            }),
            _ => Err(invalid(from, event)),
        },
        S::HalfClosedRemote => match event {
            E::SendData { end_stream: false } => Ok(S::HalfClosedRemote),
            E::SendData { end_stream: true } => Ok(S::Closed {
                reason: ClosedReason::EndStream,
            }),
            _ => Err(invalid(from, event)),
        },
        S::Closed { .. } => Err(invalid(from, event)),
    }
}