1

I am looking for a way to use a functional event-type mechanism in my project. Nothing is in production yet, so this can even be rewritten from scratch:

I want to look for 2 things:

  • Presence of value (Option<T>)
  • Functional treatment of different types, for determinism

I've done the latter, but I have some issues with the first one: For requests that look like this:

POST http://127.0.0.1:8000/publish_event HTTP/1.1
content-type: application/json

{
    "source_id": "user:asdf",
    "key": "temperature",
    "value": 25
}

I've set up the following structs and enums:

    use std::option::{Option};
    use serde::{Deserialize};
    
    #[derive(Deserialize, Debug)]
    pub struct EventPayload<T>{
        pub key: String,
        pub tag: Option<String>,
        pub value: Option<T>
    }
    
    #[derive(Deserialize, Debug)]
    #[serde(untagged)]
    pub enum EventValue {
        String(EventPayload<String>),
        Float(EventPayload<f32>),
        Int(EventPayload<i32>),
        Bool(EventPayload<bool>)
    }
    
    #[derive(Deserialize, Debug)]
    pub struct PublishSingleValueEventRequest {
        pub source_id: String,
        #[serde(flatten)]
        pub manifest: EventValue
    }

And then, I am using rocket for a simple endpoint. This is where problems appear:

#[macro_use] extern crate rocket;
use rocket::serde::json::Json;
use rocket::http::Status;

#[post("/publish_event", format="application/json", data="<request>")]
pub fn publish_event(request: Json<PublishSingleValueEventRequest>) -> Status {
    let event = request.0;
    
    /* This region does not compile
    let evt_val: Option = match event.manifest {
        EventValue::String(x) => x.value,
        EventValue::Float(x) => x.value,
        EventValue::Int(x) => x.value,
        EventValue::Bool(x) => x.value
    };

    match evt_val {
        Some(x) => println!("Event value: {:?}", x),
        None => println!("No event value provided")
    }
    */


    println!("{:?}", event.manifest);
    match event.manifest {
        EventValue::String(x) => {
            println!("String payload: {:?}", x.value);
        }

        EventValue::Float(x) => {
            println!("Float payload: {:?}", x.value);
        }

        EventValue::Int(x) => {
            println!("Int payload: {:?}", x.value);
        }
        
        EventValue::Bool(x) => {
            println!("Bool payload: {:?}", x.value);
        }
    }

    Status::Accepted
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/", routes![publish_event])
}

I would also like this to be more like

match event.manifest.value {
    Some(x) => ...,
    None => ...
}

...instead of manually unwrapping the .value Option<> fields.

ntakouris
  • 898
  • 1
  • 9
  • 22
  • 1
    I don't understand the question, all the `.value` are of different types entirely, they can not be unified. There is no possible type for `evt_val` aside from boxing the values and type-erasing to `dyn Any` or something weird like that. Also `EventPayload` seems kinda redundant here, unless it's actually used on its own you could just use "struct" variants. – Masklinn Sep 29 '21 at 07:16
  • @Masklinn I have tried `Option` but then, `Sized` is not implemented (for `value: Option`, so I can't type erase -- although I just want to check for presence. What do you propose for the redundant `EventPayload` ? – ntakouris Sep 29 '21 at 07:31
  • Hence the note that you need to *box* it aka `Option>`. – Masklinn Sep 29 '21 at 08:12
  • For the payload unless you specifically need to manipulate them you could just have `String { key: String, tag: Option, value: Option }` – Masklinn Sep 29 '21 at 08:13

2 Answers2

3

The real question here is: what do you really want to do with evt_val? If all you want is to display it, then you can map to an Option<&dyn Debug>:

let evt_val: Option::<&dyn Debug> = match event.manifest {
    EventValue::String(x) => x.value.as_ref().map (|v| v as &dyn Debug),
    EventValue::Float(x) => x.value.as_ref().map (|v| v as &dyn Debug),
    EventValue::Int(x) => x.value.as_ref().map (|v| v as &dyn Debug),
    EventValue::Bool(x) => x.value.as_ref().map (|v| v as &dyn Debug),
};

match evt_val {
    Some(x) => println!("Event value: {:?}", x),
    None => println!("No event value provided")
}
Jmb
  • 18,893
  • 2
  • 28
  • 55
  • but if you want to do anything else, you can't – ntakouris Sep 29 '21 at 07:43
  • 2
    Hence Jmb asking *what do you really want to do*. You never explain what you're actually trying to achieve, the code makes very little sense as-is and provides no insight, and thus readers can only try and guess with notoing to go from. – Masklinn Sep 29 '21 at 08:14
  • If you want to do anything else in a unified way, you will need a unified interface. In Rust a unified interface is represented by a trait. Here I've used the `Debug` trait since that's what required for printing with `{:?}`. If you wand to do anything else, you need to implement a trait `Foo` representing the behaviour you want for each case and use `Option<&Foo>` instead of `Option<&Debug>`. – Jmb Sep 29 '21 at 13:02
  • 1
    Slight note: this code will throw a warning, the correct type is `Option<&dyn Debug>`. – Cerberus Sep 29 '21 at 14:11
2

You can pattern match nested structurues:

fn main() {
    let payload: EventPayload<i32> = EventPayload {
        key: "foo".to_string(),
        tag: None,
        value: Some(10),
    };
    let value = EventValue::Int(payload);
    
    match value {
        EventValue::Int(EventPayload {value: Some(x), ..}) => {
            println!("Some value: {}", x);
        }
        EventValue::String(EventPayload {value: Some(message), ..}) => {
            println!("Some message: {}", message);
        }
        _ => {
            println!("whatever");
        }
    }
}

Playground

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • I like this. Is there any way to do this dynamically to check for `Some` on all possible enum values or am I just asking for too much? – ntakouris Sep 29 '21 at 07:44
  • 1
    @ntakouris sadly not. As a point if you want/need that you will probably want to think about how to architecture your data structures to achieve that. – Netwave Sep 29 '21 at 07:57