1use crate::error::{Error, Result};
4use serde_json::{Map, Value};
5
6pub const VALID_TYPES: &[&str] = &[
7 "req",
8 "res",
9 "stream_chunk",
10 "stream_end",
11 "cancel",
12 "error",
13];
14
15pub type Envelope = Map<String, Value>;
18
19fn require_u64(env: &Envelope, key: &str) -> Result<u64> {
20 match env.get(key).and_then(|v| v.as_i64()) {
21 Some(n) if n >= 0 => Ok(n as u64),
22 _ => Err(Error::InvalidEnvelope(format!(
23 "envelope.{key} must be a non-negative int"
24 ))),
25 }
26}
27
28pub fn validate(env: &Envelope) -> Result<()> {
29 require_u64(env, "stream_id")?;
30 require_u64(env, "seq")?;
31 let t = env
32 .get("type")
33 .and_then(|v| v.as_str())
34 .ok_or_else(|| Error::InvalidEnvelope("envelope.type missing".into()))?;
35 if !VALID_TYPES.contains(&t) {
36 return Err(Error::InvalidEnvelope(format!(
37 "envelope.type invalid: {t}"
38 )));
39 }
40 if let Some(c) = env.get("credits") {
41 match c.as_i64() {
42 Some(n) if n >= 0 => {}
43 _ => {
44 return Err(Error::InvalidEnvelope(
45 "envelope.credits must be a non-negative int".into(),
46 ));
47 }
48 }
49 }
50 if let Some(m) = env.get("method") {
51 if !m.is_string() {
52 return Err(Error::InvalidEnvelope(
53 "envelope.method must be a string".into(),
54 ));
55 }
56 }
57 if let Some(r) = env.get("reason") {
58 if !r.is_string() {
59 return Err(Error::InvalidEnvelope(
60 "envelope.reason must be a string".into(),
61 ));
62 }
63 }
64 if let Some(err) = env.get("error") {
65 let m = err
66 .as_object()
67 .ok_or_else(|| Error::InvalidEnvelope("envelope.error must be an object".into()))?;
68 if !m.get("code").map(Value::is_i64).unwrap_or(false)
69 || !m.get("message").map(Value::is_string).unwrap_or(false)
70 {
71 return Err(Error::InvalidEnvelope(
72 "envelope.error must be {code:int, message:str}".into(),
73 ));
74 }
75 }
76 Ok(())
77}
78
79pub fn encode(env: &Envelope) -> Result<Vec<u8>> {
80 validate(env)?;
81 let v: Value = Value::Object(env.clone());
82 serde_jcs::to_vec(&v).map_err(Error::Json)
83}
84
85pub fn decode(bytes: &[u8]) -> Result<Envelope> {
86 let v: Value = serde_json::from_slice(bytes)?;
87 let map = match v {
88 Value::Object(m) => m,
89 _ => {
90 return Err(Error::InvalidEnvelope(
91 "envelope must be a JSON object".into(),
92 ))
93 }
94 };
95 validate(&map)?;
96 Ok(map)
97}