1use sim_kernel::{Error, Expr, Result, Symbol};
2use sim_value::build::entry;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum CallArgumentMedia {
7 Text,
9 Bytes,
11}
12
13impl CallArgumentMedia {
14 pub fn symbol(self) -> Symbol {
16 match self {
17 Self::Text => Symbol::qualified("bridge", "Text"),
18 Self::Bytes => Symbol::qualified("bridge", "Bytes"),
19 }
20 }
21
22 fn from_symbol(symbol: &Symbol) -> Result<Self> {
23 if symbol == &Symbol::qualified("bridge", "Text") {
24 Ok(Self::Text)
25 } else if symbol == &Symbol::qualified("bridge", "Bytes") {
26 Ok(Self::Bytes)
27 } else {
28 Err(Error::Eval(format!(
29 "unknown BRIDGE call argument media {symbol}"
30 )))
31 }
32 }
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct BridgeCallArgument {
38 pub name: Symbol,
40 pub codec: Symbol,
42 pub media: CallArgumentMedia,
44 pub content_id: String,
46 pub fenced: String,
48}
49
50impl BridgeCallArgument {
51 pub fn new(
53 name: Symbol,
54 codec: Symbol,
55 media: CallArgumentMedia,
56 content_id: String,
57 fenced: String,
58 ) -> Self {
59 Self {
60 name,
61 codec,
62 media,
63 content_id,
64 fenced,
65 }
66 }
67
68 pub fn from_expr(expr: &Expr) -> Result<Self> {
70 let fields = map_fields(expr, "bridge/Call argument")?;
71 reject_unknown(fields, &["name", "codec", "media", "content-id", "fenced"])?;
72 let media = CallArgumentMedia::from_symbol(required_symbol(fields, "media")?)?;
73 Ok(Self::new(
74 required_symbol(fields, "name")?.clone(),
75 required_symbol(fields, "codec")?.clone(),
76 media,
77 required_string(fields, "content-id")?.to_owned(),
78 required_string(fields, "fenced")?.to_owned(),
79 ))
80 }
81
82 pub fn to_expr(&self) -> Expr {
84 Expr::Map(vec![
85 entry("name", Expr::Symbol(self.name.clone())),
86 entry("codec", Expr::Symbol(self.codec.clone())),
87 entry("media", Expr::Symbol(self.media.symbol())),
88 entry("content-id", Expr::String(self.content_id.clone())),
89 entry("fenced", Expr::String(self.fenced.clone())),
90 ])
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
96pub struct BridgeCallPayload {
97 pub name: Symbol,
99 pub args: Vec<BridgeCallArgument>,
101 pub model_params: Vec<(Symbol, Expr)>,
103}
104
105impl BridgeCallPayload {
106 pub fn new(name: Symbol) -> Self {
108 Self {
109 name,
110 args: Vec::new(),
111 model_params: Vec::new(),
112 }
113 }
114
115 pub fn with_arg(mut self, arg: BridgeCallArgument) -> Self {
117 self.args.push(arg);
118 self
119 }
120
121 pub fn with_model_param(mut self, name: Symbol, value: Expr) -> Self {
123 self.model_params.push((name, value));
124 self
125 }
126
127 pub fn from_expr(expr: &Expr) -> Result<Self> {
129 let fields = map_fields(expr, "bridge/Call payload")?;
130 reject_unknown(fields, &["name", "args", "model-params"])?;
131 Ok(Self {
132 name: required_symbol(fields, "name")?.clone(),
133 args: required_vector(fields, "args")?
134 .iter()
135 .map(BridgeCallArgument::from_expr)
136 .collect::<Result<Vec<_>>>()?,
137 model_params: optional_symbol_map(fields, "model-params")?,
138 })
139 }
140
141 pub fn to_expr(&self) -> Expr {
143 let mut fields = vec![
144 entry("name", Expr::Symbol(self.name.clone())),
145 entry(
146 "args",
147 Expr::Vector(self.args.iter().map(BridgeCallArgument::to_expr).collect()),
148 ),
149 ];
150 if !self.model_params.is_empty() {
151 fields.push(entry(
152 "model-params",
153 Expr::Map(
154 self.model_params
155 .iter()
156 .map(|(name, value)| (Expr::Symbol(name.clone()), value.clone()))
157 .collect(),
158 ),
159 ));
160 }
161 Expr::Map(fields)
162 }
163}
164
165pub fn validate_call_payload(payload: &Expr) -> Result<BridgeCallPayload> {
167 let payload = BridgeCallPayload::from_expr(payload)?;
168 for arg in &payload.args {
169 validate_arg(arg)?;
170 }
171 Ok(payload)
172}
173
174fn validate_arg(arg: &BridgeCallArgument) -> Result<()> {
175 if arg.content_id.trim().is_empty() {
176 return Err(Error::Eval(format!(
177 "BRIDGE call argument {} is missing a content id",
178 arg.name
179 )));
180 }
181 if !arg.fenced.contains("<sim-data-") || !arg.fenced.contains("</sim-data-") {
182 return Err(Error::Eval(format!(
183 "BRIDGE call argument {} must be fence-wrapped",
184 arg.name
185 )));
186 }
187 Ok(())
188}
189
190fn map_fields<'a>(expr: &'a Expr, label: &str) -> Result<&'a [(Expr, Expr)]> {
191 match expr {
192 Expr::Map(fields) => Ok(fields),
193 _ => Err(Error::Eval(format!("{label} must be a map"))),
194 }
195}
196
197fn reject_unknown(fields: &[(Expr, Expr)], allowed: &[&str]) -> Result<()> {
198 for (key, _) in fields {
199 let Some(name) = field_name(key) else {
200 return Err(Error::Eval(
201 "BRIDGE call field keys must be symbols".to_owned(),
202 ));
203 };
204 if !allowed.contains(&name.as_str()) {
205 return Err(Error::Eval(format!("unknown BRIDGE call field {name}")));
206 }
207 }
208 Ok(())
209}
210
211fn required_field<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
212 fields
213 .iter()
214 .find_map(|(key, value)| (field_name(key).as_deref() == Some(name)).then_some(value))
215 .ok_or_else(|| Error::Eval(format!("BRIDGE call record is missing {name}")))
216}
217
218fn required_symbol<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a Symbol> {
219 match required_field(fields, name)? {
220 Expr::Symbol(symbol) => Ok(symbol),
221 _ => Err(Error::TypeMismatch {
222 expected: "symbol",
223 found: "non-symbol",
224 }),
225 }
226}
227
228fn required_string<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a str> {
229 match required_field(fields, name)? {
230 Expr::String(value) => Ok(value),
231 _ => Err(Error::TypeMismatch {
232 expected: "string",
233 found: "non-string",
234 }),
235 }
236}
237
238fn required_vector<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
239 match required_field(fields, name)? {
240 Expr::Vector(items) | Expr::List(items) => Ok(items),
241 _ => Err(Error::Eval(format!(
242 "BRIDGE call {name} field must be a vector"
243 ))),
244 }
245}
246
247fn optional_symbol_map(fields: &[(Expr, Expr)], name: &str) -> Result<Vec<(Symbol, Expr)>> {
248 let Some(value) = fields
249 .iter()
250 .find_map(|(key, value)| (field_name(key).as_deref() == Some(name)).then_some(value))
251 else {
252 return Ok(Vec::new());
253 };
254 let Expr::Map(entries) = value else {
255 return Err(Error::Eval(format!(
256 "BRIDGE call {name} field must be a map"
257 )));
258 };
259 entries
260 .iter()
261 .map(|(key, value)| match key {
262 Expr::Symbol(symbol) => Ok((symbol.clone(), value.clone())),
263 _ => Err(Error::Eval(
264 "BRIDGE call model parameter keys must be symbols".to_owned(),
265 )),
266 })
267 .collect()
268}
269
270fn field_name(expr: &Expr) -> Option<String> {
271 match expr {
272 Expr::Symbol(symbol) => Some(symbol.name.to_string()),
273 Expr::String(value) => Some(value.clone()),
274 _ => None,
275 }
276}