main et use en global

Dans le main on définie nos deux routes, une pour créer des messages, une autre pour en récupérer un :

use std::sync::{Arc, RwLock};

#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
    use poem::{EndpointExt, get, post};
    let context = Arc::new(ExampleCtx::new());
    let routes = poem::Route::new()
        .at("/msg", post(hdl::insert_message))
        .at("/msg/:id", get(hdl::get_message))
        .data(context);
    poem::Server::new(poem::listener::TcpListener::bind("0.0.0.0:3000"))
        .run(routes)
        .await
}

Contexte et son implémentation

Le contexte utilise un RwLock pour nous permettre de lire et modifier le tableau de messages depuis plusieurs fils d’exécution différents.

struct ExampleCtx {
    messages: RwLock<Vec<model::Message>>,
}

impl ExampleCtx {
    fn new() -> Self {
        Self {
            messages: RwLock::new(vec![]),
        }
    }

    fn insert_message(&self, source: model::MessageInputDto) -> Option<usize> {
        let mut messages = self.messages.write().ok()?;
        messages.push(source.into());
        Some(messages.len() - 1)
    }

    fn get_message(&self, id: usize) -> Option<model::Message> {
        let messages = self.messages.read().ok()?;
        messages.get(id).cloned()
    }
}

Handlers de requête

C’est eux qui sont appelés lors d’une requête sur une route qui les concernent. Ils ont besoin du modèle et du contexte.

mod hdl {
    use crate::ExampleCtx;
    use crate::model;
    use poem::web::{Data, Json, Path};
    use std::sync::Arc;

    #[poem::handler]
    pub fn insert_message(
        Json(message): Json<model::MessageInputDto>,
        Data(context): Data<&Arc<ExampleCtx>>,
    ) -> Json<model::MessageOutputDto> {
        let id = context.insert_message(message).unwrap();
        let msg = context.get_message(id).unwrap();
        Json(model::MessageOutputDto {
            text: msg.text,
            created_on: msg.created_on,
            id,
        })
    }

    #[poem::handler]
    pub fn get_message(
        Path(id): Path<usize>,
        Data(context): Data<&Arc<ExampleCtx>>,
    ) -> Json<model::MessageOutputDto> {
        let msg = context.get_message(id).unwrap();
        Json(model::MessageOutputDto {
            text: msg.text,
            created_on: msg.created_on,
            id,
        })
    }
}

Modèle

C’est ici que l’on défini ce qu’est un message stocké, en transit entrant, ou en transit sortant.

mod model {
    #[derive(serde::Deserialize)]
    pub struct MessageInputDto {
        pub text: String,
    }

    #[derive(Clone)]
    pub struct Message {
        pub text: String,
        pub created_on: chrono::DateTime<chrono::Local>,
    }

    impl std::convert::From<MessageInputDto> for Message {
        fn from(value: MessageInputDto) -> Self {
            Self {
                text: value.text,
                created_on: chrono::Local::now(),
            }
        }
    }

    #[derive(serde::Serialize)]
    pub struct MessageOutputDto {
        pub id: usize,
        pub text: String,
        pub created_on: chrono::DateTime<chrono::Local>,
    }
}