subc_protocol/
call_mode.rs1use serde::{Deserialize, Deserializer, Serialize, Serializer};
4
5use crate::scope::ScopeStamp;
6
7pub const CALL_MODES_CAPABILITY: &str = "call-modes/v1";
10
11#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum CallMode {
17 Classic,
18 Codemode,
19 Flow,
20 Unknown(String),
21}
22
23impl Serialize for CallMode {
24 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
25 serializer.serialize_str(match self {
26 Self::Classic => "classic",
27 Self::Codemode => "codemode",
28 Self::Flow => "flow",
29 Self::Unknown(value) => value,
30 })
31 }
32}
33
34impl<'de> Deserialize<'de> for CallMode {
35 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
36 Ok(match String::deserialize(deserializer)?.as_str() {
37 "classic" => Self::Classic,
38 "codemode" => Self::Codemode,
39 "flow" => Self::Flow,
40 value => Self::Unknown(value.to_owned()),
41 })
42 }
43}
44
45pub fn derive_call_mode(scope: Option<&ScopeStamp>) -> CallMode {
49 match scope {
50 Some(stamp) if stamp.attributes.run_id.is_some() => CallMode::Codemode,
51 Some(stamp) if stamp.attributes.flow_id.is_some() => CallMode::Flow,
52 _ => CallMode::Classic,
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::{
60 scope::{ScopeAttributes, ScopeKind},
61 Principal,
62 };
63
64 fn stamp(attributes: ScopeAttributes) -> ScopeStamp {
65 ScopeStamp::new(Principal::Direct, "scope-1", 1, ScopeKind::Ephemeral)
66 .with_attributes(attributes)
67 }
68
69 #[test]
70 fn known_modes_round_trip_as_lowercase_strings() {
71 for (mode, wire) in [
72 (CallMode::Classic, "\"classic\""),
73 (CallMode::Codemode, "\"codemode\""),
74 (CallMode::Flow, "\"flow\""),
75 ] {
76 assert_eq!(serde_json::to_string(&mode).unwrap(), wire);
77 assert_eq!(serde_json::from_str::<CallMode>(wire).unwrap(), mode);
78 }
79 }
80
81 #[test]
82 fn unknown_modes_preserve_the_string_and_non_strings_are_refused() {
83 for value in ["future-mode", "Classic", "", "mode\nwith\"escapes"] {
84 let wire = serde_json::to_string(value).unwrap();
85 let mode = serde_json::from_str::<CallMode>(&wire).unwrap();
86 assert_eq!(mode, CallMode::Unknown(value.to_string()));
87 assert_eq!(serde_json::to_string(&mode).unwrap(), wire);
88 }
89 for wire in ["null", "1", "true", "{}", "[]"] {
90 assert!(serde_json::from_str::<CallMode>(wire).is_err());
91 }
92 }
93
94 #[test]
95 fn run_scope_derives_codemode_even_when_delegating() {
96 for delegates in [false, true] {
97 let stamp = stamp(
98 ScopeAttributes::new()
99 .with_agent_id(Some("agent-1".to_string()))
100 .with_run_id(Some("run-1".to_string()))
101 .with_delegates(delegates),
102 );
103 assert_eq!(derive_call_mode(Some(&stamp)), CallMode::Codemode);
104 }
105 }
106
107 #[test]
108 fn flow_scope_derives_flow() {
109 let stamp = stamp(ScopeAttributes::new().with_flow_id(Some("flow-1".to_string())));
110 assert_eq!(derive_call_mode(Some(&stamp)), CallMode::Flow);
111 }
112
113 #[test]
114 fn run_id_takes_precedence_over_flow_id() {
115 let stamp = stamp(
116 ScopeAttributes::new()
117 .with_run_id(Some("run-1".to_string()))
118 .with_flow_id(Some("flow-1".to_string())),
119 );
120 assert_eq!(derive_call_mode(Some(&stamp)), CallMode::Codemode);
121 }
122
123 #[test]
124 fn plain_scope_and_no_scope_derive_classic() {
125 let plain = stamp(ScopeAttributes::new());
126 let agent = stamp(
127 ScopeAttributes::new()
128 .with_agent_id(Some("agent-1".to_string()))
129 .with_delegates(true),
130 );
131 assert_eq!(derive_call_mode(Some(&plain)), CallMode::Classic);
132 assert_eq!(derive_call_mode(Some(&agent)), CallMode::Classic);
133 assert_eq!(derive_call_mode(None), CallMode::Classic);
134 }
135}