netget/protocol/spawn_context.rs
1//! Server spawn context
2//!
3//! Provides all the necessary context for spawning a protocol server.
4
5use crate::llm::actions::ParameterDefinition;
6use crate::llm::OllamaClient;
7use crate::state::app_state::AppState;
8use crate::state::ServerId;
9use std::collections::HashSet;
10use std::fmt;
11use std::net::SocketAddr;
12use std::sync::Arc;
13use tokio::sync::mpsc;
14
15/// Error returned when startup parameters supplied by the LLM or an MCP client
16/// cannot be used by the protocol.
17///
18/// These values are untrusted model/client input, so every failure is reported
19/// rather than panicking the task that is starting the server.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum StartupParamError {
22 /// A key was supplied (or accessed) that the protocol never declared in
23 /// `get_startup_parameters()`.
24 Undeclared {
25 /// Offending parameter name
26 key: String,
27 /// Parameter names the protocol does declare, sorted
28 allowed: Vec<String>,
29 /// True when the protocol code itself asked for the undeclared key
30 /// (a bug in the protocol) rather than the caller supplying it.
31 accessed_by_protocol: bool,
32 },
33 /// A required parameter was absent or held the wrong JSON type.
34 Invalid {
35 /// Offending parameter name
36 key: String,
37 /// Human readable description of what went wrong
38 detail: String,
39 },
40}
41
42impl fmt::Display for StartupParamError {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 match self {
45 StartupParamError::Undeclared {
46 key,
47 allowed,
48 accessed_by_protocol,
49 } => {
50 if *accessed_by_protocol {
51 write!(
52 f,
53 "Attempted to access undeclared startup parameter '{}'. Protocol must declare this parameter in get_startup_parameters(). Allowed parameters: {:?}",
54 key, allowed
55 )
56 } else {
57 write!(
58 f,
59 "Undeclared startup parameter '{}'. Protocol must declare this parameter in get_startup_parameters(). Allowed parameters: {:?}",
60 key, allowed
61 )
62 }
63 }
64 StartupParamError::Invalid { key, detail } => {
65 write!(f, "Invalid startup parameter '{}': {}", key, detail)
66 }
67 }
68 }
69}
70
71impl std::error::Error for StartupParamError {}
72
73/// Convenience alias for fallible startup-parameter access.
74pub type StartupParamResult<T> = std::result::Result<T, StartupParamError>;
75
76/// Type-safe wrapper for startup parameters
77///
78/// Validates that parameters can only be accessed if they were declared
79/// in the protocol's `get_startup_parameters()` implementation.
80///
81/// Every accessor returns a [`StartupParamResult`]: the JSON originates from the
82/// LLM (`open_server`) or an MCP client (`start_server`), so malformed values
83/// must surface as errors to the caller instead of aborting the task.
84#[derive(Clone)]
85pub struct StartupParams {
86 /// The actual JSON parameter values provided by the LLM
87 params: serde_json::Value,
88 /// Set of allowed parameter names (from ParameterDefinition)
89 allowed_params: HashSet<String>,
90}
91
92/// Maximum retained startup JSON content, including keys and node overhead.
93pub const MAX_STARTUP_PARAM_BYTES: usize = 8 * 1024 * 1024;
94/// Maximum nodes in a constructed startup parameter object.
95pub const MAX_STARTUP_PARAM_NODES: usize = 65_536;
96/// Maximum nesting depth in a constructed startup parameter object.
97pub const MAX_STARTUP_PARAM_DEPTH: usize = 64;
98
99impl fmt::Debug for StartupParams {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.debug_struct("StartupParams")
102 .field(
103 "params",
104 &crate::utils::redact::redact_sensitive(&self.params),
105 )
106 .field("allowed_params", &self.allowed_params)
107 .finish()
108 }
109}
110
111impl StartupParams {
112 /// Check borrowed JSON before cloning or formatting untrusted startup data.
113 pub fn preflight(params: &serde_json::Value) -> StartupParamResult<()> {
114 if crate::utils::json_budget::within_budget(
115 params,
116 MAX_STARTUP_PARAM_BYTES,
117 MAX_STARTUP_PARAM_NODES,
118 MAX_STARTUP_PARAM_DEPTH,
119 ) {
120 Ok(())
121 } else {
122 Err(StartupParamError::Invalid {
123 key: "startup_params".into(),
124 detail: "startup_params exceeds the JSON size, node or depth budget".into(),
125 })
126 }
127 }
128
129 /// Refuse an excessive owned value without recursively dropping its children.
130 /// Call this before an earlier error path can dispose of a constructed value.
131 pub fn preflight_owned(params: &mut Option<serde_json::Value>) -> StartupParamResult<()> {
132 if let Some(value) = params.as_ref() {
133 if let Err(error) = Self::preflight(value) {
134 crate::utils::json_budget::drop_iteratively(params.take().expect("present above"));
135 return Err(error);
136 }
137 }
138 Ok(())
139 }
140
141 /// Create new StartupParams with validation
142 ///
143 /// # Arguments
144 /// * `params` - JSON object containing parameter values
145 /// * `schema` - Parameter definitions from protocol's `get_startup_parameters()`
146 ///
147 /// # Errors
148 /// Returns [`StartupParamError::Undeclared`] if any key in `params` is not
149 /// defined in `schema`, or [`StartupParamError::Invalid`] if `params` is not
150 /// an object. An absent parameter set should be represented by `None` at the caller.
151 pub fn new(
152 params: serde_json::Value,
153 schema: Vec<ParameterDefinition>,
154 ) -> StartupParamResult<Self> {
155 if let Err(error) = Self::preflight(¶ms) {
156 crate::utils::json_budget::drop_iteratively(params);
157 return Err(error);
158 }
159 let allowed_params: HashSet<String> = schema.iter().map(|p| p.name.clone()).collect();
160
161 let obj = params
162 .as_object()
163 .ok_or_else(|| StartupParamError::Invalid {
164 key: "startup_params".to_string(),
165 detail: "startup_params must be a JSON object".to_string(),
166 })?;
167 // Validate that all provided parameters are in the schema.
168 for key in obj.keys() {
169 if !allowed_params.contains(key) {
170 return Err(StartupParamError::Undeclared {
171 key: key.clone(),
172 allowed: sorted(&allowed_params),
173 accessed_by_protocol: false,
174 });
175 }
176 }
177
178 Ok(Self {
179 params,
180 allowed_params,
181 })
182 }
183
184 /// Validate declared JSON shapes and required fields before any resource is
185 /// stopped or registered. Protocol-specific I/O checks still occur at spawn.
186 pub fn new_validated(
187 params: serde_json::Value,
188 schema: Vec<ParameterDefinition>,
189 ) -> StartupParamResult<Self> {
190 let result = Self::new(params, schema.clone())?;
191 for parameter in &schema {
192 let value = result.params.get(¶meter.name);
193 if value.is_none_or(serde_json::Value::is_null) {
194 if parameter.required {
195 return Err(result.invalid(
196 ¶meter.name,
197 "required parameter is missing or null".into(),
198 ));
199 }
200 continue;
201 }
202 let value = value.expect("present above");
203 let hint = parameter.type_hint.replace(" or ", "|");
204 if !hint
205 .split('|')
206 .any(|kind| parameter_type_matches(value, kind.trim()))
207 {
208 return Err(result.invalid(
209 ¶meter.name,
210 format!(
211 "expected {}, got {}",
212 parameter.type_hint,
213 diagnostic_value(¶meter.name, value)
214 ),
215 ));
216 }
217 }
218 Ok(result)
219 }
220
221 /// Names of the parameters this protocol declares, sorted.
222 pub fn allowed_parameters(&self) -> Vec<String> {
223 sorted(&self.allowed_params)
224 }
225
226 /// Get a required string parameter
227 ///
228 /// # Errors
229 /// - If the parameter was not declared in `get_startup_parameters()`
230 /// - If the parameter is missing
231 /// - If the parameter is not a string
232 pub fn get_string(&self, key: &str) -> StartupParamResult<String> {
233 self.validate_key(key)?;
234 match self.params.get(key).and_then(|v| v.as_str()) {
235 Some(s) => Ok(s.to_string()),
236 None => Err(self.invalid(
237 key,
238 format!(
239 "Required string parameter '{}' is missing or not a string. Params: {}",
240 key,
241 crate::utils::redact::redact_sensitive(&self.params)
242 ),
243 )),
244 }
245 }
246
247 /// Get an optional string parameter
248 ///
249 /// # Errors
250 /// - If the parameter was not declared in `get_startup_parameters()`
251 /// - If the parameter exists but is not a string
252 pub fn get_optional_string(&self, key: &str) -> StartupParamResult<Option<String>> {
253 self.validate_key(key)?;
254 match self.params.get(key) {
255 None | Some(serde_json::Value::Null) => Ok(None),
256 Some(v) => match v.as_str() {
257 Some(s) => Ok(Some(s.to_string())),
258 None => Err(self.invalid(
259 key,
260 format!(
261 "Optional string parameter '{}' exists but is not a string. Value: {}",
262 key,
263 diagnostic_value(key, v)
264 ),
265 )),
266 },
267 }
268 }
269
270 /// Get a required boolean parameter
271 ///
272 /// # Errors
273 /// - If the parameter was not declared in `get_startup_parameters()`
274 /// - If the parameter is missing
275 /// - If the parameter is not a boolean
276 pub fn get_bool(&self, key: &str) -> StartupParamResult<bool> {
277 self.validate_key(key)?;
278 match self.params.get(key).and_then(|v| v.as_bool()) {
279 Some(b) => Ok(b),
280 None => Err(self.invalid(
281 key,
282 format!(
283 "Required boolean parameter '{}' is missing or not a boolean. Params: {}",
284 key,
285 crate::utils::redact::redact_sensitive(&self.params)
286 ),
287 )),
288 }
289 }
290
291 /// Get an optional boolean parameter
292 ///
293 /// # Errors
294 /// - If the parameter was not declared in `get_startup_parameters()`
295 /// - If the parameter exists but is not a boolean
296 pub fn get_optional_bool(&self, key: &str) -> StartupParamResult<Option<bool>> {
297 self.validate_key(key)?;
298 match self.params.get(key) {
299 None | Some(serde_json::Value::Null) => Ok(None),
300 Some(v) => match v.as_bool() {
301 Some(b) => Ok(Some(b)),
302 None => Err(self.invalid(
303 key,
304 format!(
305 "Optional boolean parameter '{}' exists but is not a boolean. Value: {}",
306 key,
307 diagnostic_value(key, v)
308 ),
309 )),
310 },
311 }
312 }
313
314 /// Get a required integer parameter
315 ///
316 /// # Errors
317 /// - If the parameter was not declared in `get_startup_parameters()`
318 /// - If the parameter is missing
319 /// - If the parameter is not an integer
320 pub fn get_i64(&self, key: &str) -> StartupParamResult<i64> {
321 self.validate_key(key)?;
322 match self.params.get(key).and_then(|v| v.as_i64()) {
323 Some(n) => Ok(n),
324 None => Err(self.invalid(
325 key,
326 format!(
327 "Required integer parameter '{}' is missing or not an integer. Params: {}",
328 key,
329 crate::utils::redact::redact_sensitive(&self.params)
330 ),
331 )),
332 }
333 }
334
335 /// Get an optional integer parameter
336 ///
337 /// # Errors
338 /// - If the parameter was not declared in `get_startup_parameters()`
339 /// - If the parameter exists but is not an integer
340 pub fn get_optional_i64(&self, key: &str) -> StartupParamResult<Option<i64>> {
341 self.validate_key(key)?;
342 match self.params.get(key) {
343 None | Some(serde_json::Value::Null) => Ok(None),
344 Some(v) => match v.as_i64() {
345 Some(n) => Ok(Some(n)),
346 None => Err(self.invalid(
347 key,
348 format!(
349 "Optional integer parameter '{}' exists but is not an integer. Value: {}",
350 key,
351 diagnostic_value(key, v)
352 ),
353 )),
354 },
355 }
356 }
357
358 /// Get an optional number parameter (integer or fraction)
359 ///
360 /// # Errors
361 /// - If the parameter was not declared in `get_startup_parameters()`
362 /// - If the parameter exists but is not a number
363 pub fn get_optional_f64(&self, key: &str) -> StartupParamResult<Option<f64>> {
364 self.validate_key(key)?;
365 match self.params.get(key) {
366 None | Some(serde_json::Value::Null) => Ok(None),
367 Some(v) => match v.as_f64() {
368 Some(n) => Ok(Some(n)),
369 None => Err(self.invalid(
370 key,
371 format!(
372 "Optional number parameter '{}' exists but is not a number. Value: {}",
373 key,
374 diagnostic_value(key, v)
375 ),
376 )),
377 },
378 }
379 }
380
381 /// Get a required unsigned integer parameter
382 ///
383 /// # Errors
384 /// - If the parameter was not declared in `get_startup_parameters()`
385 /// - If the parameter is missing
386 /// - If the parameter is not an unsigned integer
387 pub fn get_u64(&self, key: &str) -> StartupParamResult<u64> {
388 self.validate_key(key)?;
389 match self.params.get(key).and_then(|v| v.as_u64()) {
390 Some(n) => Ok(n),
391 None => Err(self.invalid(
392 key,
393 format!(
394 "Required unsigned integer parameter '{}' is missing or not an unsigned integer. Params: {}",
395 key, crate::utils::redact::redact_sensitive(&self.params)
396 ),
397 )),
398 }
399 }
400
401 /// Get an optional unsigned integer parameter
402 ///
403 /// # Errors
404 /// - If the parameter was not declared in `get_startup_parameters()`
405 /// - If the parameter exists but is not an unsigned integer
406 pub fn get_optional_u64(&self, key: &str) -> StartupParamResult<Option<u64>> {
407 self.validate_key(key)?;
408 match self.params.get(key) {
409 None | Some(serde_json::Value::Null) => Ok(None),
410 Some(v) => match v.as_u64() {
411 Some(n) => Ok(Some(n)),
412 None => Err(self.invalid(
413 key,
414 format!(
415 "Optional unsigned integer parameter '{}' exists but is not an unsigned integer. Value: {}",
416 key, diagnostic_value(key, v)
417 ),
418 )),
419 },
420 }
421 }
422
423 /// Get an optional u32 parameter
424 ///
425 /// # Errors
426 /// - If the parameter was not declared in `get_startup_parameters()`
427 /// - If the parameter exists but is not an unsigned integer or exceeds u32::MAX
428 pub fn get_optional_u32(&self, key: &str) -> StartupParamResult<Option<u32>> {
429 self.validate_key(key)?;
430 match self.params.get(key) {
431 None | Some(serde_json::Value::Null) => Ok(None),
432 Some(v) => {
433 let val = match v.as_u64() {
434 Some(n) => n,
435 None => {
436 return Err(self.invalid(
437 key,
438 format!(
439 "Optional u32 parameter '{}' exists but is not an unsigned integer. Value: {}",
440 key, diagnostic_value(key, v)
441 ),
442 ))
443 }
444 };
445 if val > u32::MAX as u64 {
446 return Err(self.invalid(
447 key,
448 format!(
449 "Optional u32 parameter '{}' exceeds u32::MAX ({}). Value: {}",
450 key,
451 u32::MAX,
452 diagnostic_value(key, v)
453 ),
454 ));
455 }
456 Ok(Some(val as u32))
457 }
458 }
459 }
460
461 /// Get a required object/map parameter
462 ///
463 /// # Errors
464 /// - If the parameter was not declared in `get_startup_parameters()`
465 /// - If the parameter is missing
466 /// - If the parameter is not an object
467 pub fn get_object(
468 &self,
469 key: &str,
470 ) -> StartupParamResult<&serde_json::Map<String, serde_json::Value>> {
471 self.validate_key(key)?;
472 match self.params.get(key).and_then(|v| v.as_object()) {
473 Some(o) => Ok(o),
474 None => Err(self.invalid(
475 key,
476 format!(
477 "Required object parameter '{}' is missing or not an object. Params: {}",
478 key,
479 crate::utils::redact::redact_sensitive(&self.params)
480 ),
481 )),
482 }
483 }
484
485 /// Get an optional object/map parameter
486 ///
487 /// # Errors
488 /// - If the parameter was not declared in `get_startup_parameters()`
489 /// - If the parameter exists but is not an object
490 pub fn get_optional_object(
491 &self,
492 key: &str,
493 ) -> StartupParamResult<Option<&serde_json::Map<String, serde_json::Value>>> {
494 self.validate_key(key)?;
495 match self.params.get(key) {
496 None | Some(serde_json::Value::Null) => Ok(None),
497 Some(v) => match v.as_object() {
498 Some(o) => Ok(Some(o)),
499 None => Err(self.invalid(
500 key,
501 format!(
502 "Optional object parameter '{}' exists but is not an object. Value: {}",
503 key,
504 diagnostic_value(key, v)
505 ),
506 )),
507 },
508 }
509 }
510
511 /// Get a required array parameter
512 ///
513 /// # Errors
514 /// - If the parameter was not declared in `get_startup_parameters()`
515 /// - If the parameter is missing
516 /// - If the parameter is not an array
517 pub fn get_array(&self, key: &str) -> StartupParamResult<&Vec<serde_json::Value>> {
518 self.validate_key(key)?;
519 match self.params.get(key).and_then(|v| v.as_array()) {
520 Some(a) => Ok(a),
521 None => Err(self.invalid(
522 key,
523 format!(
524 "Required array parameter '{}' is missing or not an array. Params: {}",
525 key,
526 crate::utils::redact::redact_sensitive(&self.params)
527 ),
528 )),
529 }
530 }
531
532 /// Get an optional array parameter
533 ///
534 /// # Errors
535 /// - If the parameter was not declared in `get_startup_parameters()`
536 /// - If the parameter exists but is not an array
537 pub fn get_optional_array(
538 &self,
539 key: &str,
540 ) -> StartupParamResult<Option<&Vec<serde_json::Value>>> {
541 self.validate_key(key)?;
542 match self.params.get(key) {
543 None | Some(serde_json::Value::Null) => Ok(None),
544 Some(v) => match v.as_array() {
545 Some(a) => Ok(Some(a)),
546 None => Err(self.invalid(
547 key,
548 format!(
549 "Optional array parameter '{}' exists but is not an array. Value: {}",
550 key,
551 diagnostic_value(key, v)
552 ),
553 )),
554 },
555 }
556 }
557
558 /// Build an `Invalid` error for `key`.
559 fn invalid(&self, key: &str, detail: String) -> StartupParamError {
560 StartupParamError::Invalid {
561 key: key.to_string(),
562 detail,
563 }
564 }
565
566 /// Validate that a key was declared in get_startup_parameters()
567 ///
568 /// # Errors
569 /// If the key is not in the allowed parameters set
570 fn validate_key(&self, key: &str) -> StartupParamResult<()> {
571 if !self.allowed_params.contains(key) {
572 return Err(StartupParamError::Undeclared {
573 key: key.to_string(),
574 allowed: sorted(&self.allowed_params),
575 accessed_by_protocol: true,
576 });
577 }
578 Ok(())
579 }
580}
581
582/// A parameter's value as an error message may show it: masked by the same rule the
583/// redactor applies to a key/value pair (`is_shown_redacted`, which also hides a string
584/// `auth`), and otherwise with any credential nested inside it masked.
585fn diagnostic_value(key: &str, value: &serde_json::Value) -> serde_json::Value {
586 if crate::utils::redact::is_shown_redacted(key, value) {
587 serde_json::Value::String(crate::utils::redact::REDACTED.into())
588 } else {
589 crate::utils::redact::redact_sensitive(value)
590 }
591}
592
593/// Sorted copy of a name set, so error messages are deterministic.
594fn sorted(set: &HashSet<String>) -> Vec<String> {
595 let mut v: Vec<String> = set.iter().cloned().collect();
596 v.sort();
597 v
598}
599
600/// Context passed to protocol servers during spawning
601///
602/// Contains all the dependencies and configuration needed to start a server.
603#[derive(Clone)]
604pub struct SpawnContext {
605 /// Address to listen on (DEPRECATED - use host/port fields instead)
606 ///
607 /// This field is maintained for backwards compatibility with unmigrated protocols.
608 /// New protocols should use the flexible binding system (mac_address, interface, host, port).
609 #[deprecated(since = "1.0.0", note = "Use host/port fields instead")]
610 pub listen_addr: SocketAddr,
611
612 // === NEW FLEXIBLE BINDING FIELDS ===
613 /// MAC address for Layer 2 protocols (e.g., ARP spoofing with specific MAC)
614 ///
615 /// Protocol defaults are already applied. Use this value directly.
616 pub mac_address: Option<String>,
617
618 /// Network interface for raw protocols (e.g., "lo", "eth0", "en0")
619 ///
620 /// Protocol defaults are already applied. Use this value directly.
621 pub interface: Option<String>,
622
623 /// Host address (IPv4, IPv6, or hostname) for socket-based protocols
624 ///
625 /// Protocol defaults are already applied. Use this value directly.
626 /// Examples: "127.0.0.1", "0.0.0.0", "::", "localhost"
627 pub host: Option<String>,
628
629 /// Port number for socket-based protocols
630 ///
631 /// Protocol defaults are already applied. Use this value directly.
632 /// Some(0) means automatic port assignment.
633 pub port: Option<u16>,
634
635 /// LLM client for generating responses
636 pub llm_client: OllamaClient,
637
638 /// Application state
639 pub state: Arc<AppState>,
640
641 /// Channel for sending status updates to UI
642 pub status_tx: mpsc::UnboundedSender<String>,
643
644 /// Unique identifier for this server instance
645 pub server_id: ServerId,
646
647 /// Optional type-safe startup parameters specific to the protocol
648 ///
649 /// Parameters can only be accessed if they were declared in the protocol's
650 /// `get_startup_parameters()` implementation. Accessing an undeclared
651 /// parameter, or one holding the wrong JSON type, returns a
652 /// [`StartupParamError`] rather than panicking.
653 ///
654 /// For example:
655 /// - HTTP Proxy: certificate_mode, request_filter_mode, response_filter_mode
656 /// - gRPC: proto_schema, enable_reflection
657 /// - DataLink: interface, filter
658 pub startup_params: Option<StartupParams>,
659}
660
661impl SpawnContext {
662 /// Helper method to get socket address from host and port
663 ///
664 /// Port-based protocols can use this to construct a SocketAddr from
665 /// the flexible binding fields.
666 ///
667 /// # Returns
668 /// * `Some(SocketAddr)` - If both host and port are available
669 /// * `None` - If host or port is missing
670 ///
671 /// # Example
672 /// ```ignore
673 /// let addr = ctx.socket_addr().context("TCP requires host and port")?;
674 /// ```
675 pub fn socket_addr(&self) -> Option<SocketAddr> {
676 match (&self.host, self.port) {
677 (Some(host), Some(port)) => host
678 .parse::<std::net::IpAddr>()
679 .ok()
680 .map(|ip| SocketAddr::new(ip, port)),
681 _ => None,
682 }
683 }
684
685 /// Helper method to get interface name
686 ///
687 /// Interface-based protocols can use this to get the interface name
688 /// with proper error context.
689 ///
690 /// # Returns
691 /// * `Some(&str)` - If interface is available
692 /// * `None` - If interface is not set
693 ///
694 /// # Example
695 /// ```ignore
696 /// let interface = ctx.interface()
697 /// .context("ICMP requires network interface")?;
698 /// ```
699 pub fn interface(&self) -> Option<&str> {
700 self.interface.as_deref()
701 }
702
703 /// Helper method to get MAC address
704 ///
705 /// Layer 2 protocols can use this to get the MAC address
706 /// with proper error context.
707 ///
708 /// # Returns
709 /// * `Some(&str)` - If MAC address is available
710 /// * `None` - If MAC address is not set
711 ///
712 /// # Example
713 /// ```ignore
714 /// let mac = ctx.mac_address()
715 /// .context("ARP spoofing requires MAC address")?;
716 /// ```
717 pub fn mac_address(&self) -> Option<&str> {
718 self.mac_address.as_deref()
719 }
720
721 /// Get the legacy listen address (for unmigrated protocols)
722 ///
723 /// This method provides access to the deprecated `listen_addr` field without
724 /// triggering deprecation warnings. Use this during migration or for protocols
725 /// that haven't been migrated to the flexible binding system yet.
726 ///
727 /// Once a protocol is migrated to use `socket_addr()`, `interface()`, or
728 /// `mac_address()`, it should no longer use this method.
729 ///
730 /// # Example
731 /// ```ignore
732 /// // Unmigrated protocol:
733 /// let addr = ctx.legacy_listen_addr();
734 /// TcpListener::bind(addr).await?;
735 ///
736 /// // After migration:
737 /// let addr = ctx.socket_addr().context("requires host and port")?;
738 /// TcpListener::bind(addr).await?;
739 /// ```
740 #[allow(deprecated)]
741 pub fn legacy_listen_addr(&self) -> SocketAddr {
742 self.listen_addr
743 }
744}
745
746/// Type hints intentionally admit unions and typed arrays. Unknown descriptive
747/// hints retain protocol validation rather than inventing an interpretation.
748fn parameter_type_matches(value: &serde_json::Value, kind: &str) -> bool {
749 match kind {
750 "string" => value.is_string(),
751 "number" => value.is_number(),
752 "integer" => value.is_i64() || value.is_u64(),
753 "boolean" => value.is_boolean(),
754 "object" => value.is_object(),
755 "array" => value.is_array(),
756 "null" => value.is_null(),
757 "array of strings" => value
758 .as_array()
759 .is_some_and(|items| items.iter().all(|item| item.is_string())),
760 "array of objects" => value
761 .as_array()
762 .is_some_and(|items| items.iter().all(|item| item.is_object())),
763 "array of integers" => value
764 .as_array()
765 .is_some_and(|items| items.iter().all(|item| item.is_i64() || item.is_u64())),
766 "array of booleans" => value
767 .as_array()
768 .is_some_and(|items| items.iter().all(|item| item.is_boolean())),
769 _ => true,
770 }
771}