crux_http 0.2.0

HTTP capability for use with crux_core
Documentation
mod shared {
    use crux_core::render::Render;
    use crux_http::Http;
    use crux_macros::Effect;
    use serde::{Deserialize, Serialize};

    #[derive(Default)]
    pub(crate) struct App;

    #[derive(Serialize, Deserialize)]
    pub enum Event {
        Get,
        GetJson,
        Set(crux_http::Result<crux_http::Response<Vec<u8>>>),
        SetJson(crux_http::Result<crux_http::Response<String>>),
    }

    #[derive(Default, Serialize, Deserialize)]
    pub struct Model {
        pub status: u16,
        pub body: Vec<u8>,
        pub json_body: String,
    }

    #[derive(Serialize, Deserialize, Default)]
    pub struct ViewModel {
        pub result: String,
    }

    impl crux_core::App for App {
        type Event = Event;
        type Model = Model;
        type ViewModel = ViewModel;

        type Capabilities = Capabilities;

        fn update(&self, event: Event, model: &mut Model, caps: &Capabilities) {
            match event {
                Event::Get => {
                    caps.http.get("http://example.com").send(Event::Set);
                }
                Event::GetJson => {
                    caps.http
                        .get("http://example.com")
                        .expect_json::<String>()
                        .send(Event::SetJson);
                }
                Event::Set(response) => {
                    let mut response = response.unwrap();
                    model.status = response.status().into();
                    model.body = response.take_body().unwrap();
                    caps.render.render()
                }
                Event::SetJson(response) => {
                    model.json_body = response.unwrap().take_body().unwrap();
                    caps.render.render()
                }
            }
        }

        fn view(&self, model: &Self::Model) -> Self::ViewModel {
            ViewModel {
                result: format!(
                    "Status: {}, Body: {}, Json Body: {}",
                    model.status,
                    String::from_utf8_lossy(&model.body),
                    &model.json_body
                ),
            }
        }
    }

    #[derive(Effect)]
    pub(crate) struct Capabilities {
        pub http: Http<Event>,
        pub render: Render<Event>,
    }
}

mod shell {
    use super::shared::{App, Effect, Event, ViewModel};
    use anyhow::Result;
    use crux_core::{Core, Request};
    use crux_http::protocol::{HttpRequest, HttpResponse};
    use std::collections::VecDeque;

    pub enum Outcome {
        Http(HttpResponse),
    }

    enum CoreMessage {
        Event(Event),
        Response(Vec<u8>, Outcome),
    }

    pub fn run(event: Event) -> Result<(Vec<Effect>, ViewModel)> {
        let core: Core<Effect, App> = Core::default();
        let mut queue: VecDeque<CoreMessage> = VecDeque::new();

        queue.push_back(CoreMessage::Event(event));

        let mut received = vec![];

        while !queue.is_empty() {
            let msg = queue.pop_front();

            let reqs = match msg {
                Some(CoreMessage::Event(m)) => core.process_event(&bcs::to_bytes(&m)?),
                Some(CoreMessage::Response(uuid, output)) => core.handle_response(
                    &uuid,
                    &match output {
                        Outcome::Http(x) => bcs::to_bytes(&x)?,
                    },
                ),
                _ => vec![],
            };
            let reqs: Vec<Request<Effect>> = bcs::from_bytes(&reqs)?;

            for Request { uuid, effect } in reqs {
                match effect {
                    Effect::Render(_) => received.push(effect.clone()),
                    Effect::Http(HttpRequest { .. }) => {
                        received.push(effect);
                        queue.push_back(CoreMessage::Response(
                            uuid,
                            Outcome::Http(HttpResponse {
                                status: 200,
                                body: "\"Hello\"".as_bytes().to_owned(),
                            }),
                        ));
                    }
                }
            }
        }

        let view = bcs::from_bytes::<ViewModel>(&core.view())?;
        Ok((received, view))
    }
}

mod tests {
    use crate::{
        shared::{Effect, Event},
        shell::run,
    };
    use anyhow::Result;
    use crux_core::render::RenderOperation;
    use crux_http::protocol::HttpRequest;

    #[test]
    pub fn test_http() -> Result<()> {
        let (received, view) = run(Event::Get)?;
        assert_eq!(
            received,
            vec![
                Effect::Http(HttpRequest {
                    method: "GET".to_string(),
                    url: "http://example.com/".to_string()
                }),
                Effect::Render(RenderOperation)
            ]
        );
        assert_eq!(view.result, "Status: 200, Body: \"Hello\", Json Body: ");
        Ok(())
    }

    #[test]
    pub fn test_http_json() -> Result<()> {
        let (received, view) = run(Event::GetJson)?;
        assert_eq!(
            received,
            vec![
                Effect::Http(HttpRequest {
                    method: "GET".to_string(),
                    url: "http://example.com/".to_string()
                }),
                Effect::Render(RenderOperation)
            ]
        );
        assert_eq!(view.result, "Status: 0, Body: , Json Body: Hello");
        Ok(())
    }
}