Skip to main content

orpc_procedure/
output.rs

1use serde::Serialize;
2
3use crate::error::{ProcedureError, SerializeError};
4
5/// Type-erased output — wraps a serializable value.
6///
7/// The concrete output type is erased via `erased_serde::Serialize`,
8/// allowing heterogeneous procedure outputs in the type-erased layer.
9pub struct DynOutput(Box<dyn erased_serde::Serialize + Send>);
10
11impl DynOutput {
12    /// Create from any serializable type.
13    pub fn new<T: Serialize + Send + 'static>(value: T) -> Self {
14        DynOutput(Box::new(value))
15    }
16
17    /// Serialize to a JSON value.
18    pub fn to_value(&self) -> Result<serde_json::Value, ProcedureError> {
19        serde_json::to_value(&self.0)
20            .map_err(|e| ProcedureError::Serialize(SerializeError::from(e)))
21    }
22
23    /// Serialize directly to a writer (for streaming responses).
24    pub fn serialize_to<W: std::io::Write>(&self, writer: W) -> Result<(), ProcedureError> {
25        serde_json::to_writer(writer, &self.0)
26            .map_err(|e| ProcedureError::Serialize(SerializeError::from(e)))
27    }
28}
29
30impl Serialize for DynOutput {
31    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
32        erased_serde::serialize(&*self.0, serializer)
33    }
34}
35
36impl std::fmt::Debug for DynOutput {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("DynOutput").finish_non_exhaustive()
39    }
40}
41
42#[cfg(test)]
43mod tests {
44    use super::*;
45    use serde::Deserialize;
46
47    #[derive(Debug, Serialize, Deserialize, PartialEq)]
48    struct Planet {
49        name: String,
50        radius: u32,
51    }
52
53    #[test]
54    fn new_and_to_value_string() {
55        let output = DynOutput::new("hello".to_string());
56        let value = output.to_value().unwrap();
57        assert_eq!(value, serde_json::json!("hello"));
58    }
59
60    #[test]
61    fn new_and_to_value_struct() {
62        let output = DynOutput::new(Planet {
63            name: "Earth".into(),
64            radius: 6371,
65        });
66        let value = output.to_value().unwrap();
67        assert_eq!(value, serde_json::json!({"name": "Earth", "radius": 6371}));
68    }
69
70    #[test]
71    fn new_and_to_value_vec() {
72        let output = DynOutput::new(vec![1, 2, 3]);
73        let value = output.to_value().unwrap();
74        assert_eq!(value, serde_json::json!([1, 2, 3]));
75    }
76
77    #[test]
78    fn serialize_to_writer() {
79        let output = DynOutput::new(42u32);
80        let mut buf = Vec::new();
81        output.serialize_to(&mut buf).unwrap();
82        assert_eq!(std::str::from_utf8(&buf).unwrap(), "42");
83    }
84
85    #[test]
86    fn serde_serialize_impl() {
87        let output = DynOutput::new(Planet {
88            name: "Mars".into(),
89            radius: 3389,
90        });
91        let json = serde_json::to_string(&output).unwrap();
92        let parsed: Planet = serde_json::from_str(&json).unwrap();
93        assert_eq!(
94            parsed,
95            Planet {
96                name: "Mars".into(),
97                radius: 3389
98            }
99        );
100    }
101
102    #[test]
103    fn debug_output() {
104        let output = DynOutput::new(42u32);
105        let debug = format!("{output:?}");
106        assert!(debug.contains("DynOutput"));
107    }
108
109    #[test]
110    fn dyn_output_is_send() {
111        fn assert_send<T: Send>() {}
112        assert_send::<DynOutput>();
113    }
114}