cratestack_axum/rpc/
procedure_subscription.rs1use cratestack_core::{CratestackError, SubscriptionStream};
17use futures_util::stream::{self, StreamExt};
18use serde::Serialize;
19use serde::de::DeserializeOwned;
20
21pub fn procedure_input_query(input: serde_json::Value) -> Result<Option<String>, CratestackError> {
24 let object = match input {
25 serde_json::Value::Null => return Ok(None),
26 serde_json::Value::Object(object) => object,
27 _ => {
28 return Err(CratestackError::BadRequest(
29 "a subscription procedure's input is an object of its arguments".to_owned(),
30 ));
31 }
32 };
33 if object.is_empty() {
34 return Ok(None);
35 }
36 let mut query = url::form_urlencoded::Serializer::new(String::new());
37 for (name, value) in &object {
38 query.append_pair(name, &value.to_string());
39 }
40 Ok(Some(query.finish()))
41}
42
43pub fn procedure_query_args<A: DeserializeOwned>(
46 query: Option<&str>,
47) -> Result<A, CratestackError> {
48 let mut object = serde_json::Map::new();
49 for (name, value) in crate::parse_query_pairs(query)? {
50 let value = serde_json::from_str(&value).map_err(|_| {
51 CratestackError::BadRequest(format!(
52 "argument `{name}` must be JSON, such as `{name}={{\"id\":1}}` or `{name}=\"text\"`"
53 ))
54 })?;
55 if object.insert(name.clone(), value).is_some() {
56 return Err(CratestackError::BadRequest(format!(
57 "argument `{name}` is given twice"
58 )));
59 }
60 }
61 serde_json::from_value(serde_json::Value::Object(object)).map_err(|error| {
62 CratestackError::BadRequest(format!("invalid subscription arguments: {error}"))
63 })
64}
65
66pub fn procedure_feed<T>(items: SubscriptionStream<T>) -> SubscriptionStream<serde_json::Value>
68where
69 T: Serialize + Send + 'static,
70{
71 Box::pin(stream::unfold(Some(items), |items| async move {
72 let mut items = items?;
73 let item = match items.next().await? {
74 Ok(item) => serde_json::to_value(&item).map_err(|error| {
75 CratestackError::Codec(format!("failed to encode a subscription item: {error}"))
76 }),
77 Err(error) => Err(error),
78 };
79 let rest = item.is_ok().then_some(items);
80 Some((item, rest))
81 }))
82}
83
84#[cfg(test)]
85mod tests;