Skip to main content

systemprompt_models/profile/gateway/
state.rs

1//! Lifecycle wrapper for the gateway section of a profile.
2//!
3//! YAML deserialization always produces [`GatewayState::Spec`]; the
4//! profile loader projects it to [`GatewayState::Resolved`]. Runtime read
5//! paths must observe [`GatewayState::Resolved`] — they consult
6//! [`Self::resolved`] which logs and returns `None` if the loader has not run.
7//!
8//! Copyright (c) systemprompt.io — Business Source License 1.1.
9//! See <https://systemprompt.io> for licensing details.
10
11use std::borrow::Cow;
12
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use systemprompt_identifiers::RouteId;
15
16use super::super::providers::ProviderRegistry;
17use super::config::{GatewayConfig, GatewayConfigSpec};
18
19#[derive(Debug, Clone)]
20pub enum GatewayState {
21    Spec(GatewayConfigSpec),
22    Resolved(GatewayConfig),
23}
24
25impl GatewayState {
26    #[must_use]
27    pub fn resolved(&self) -> Option<&GatewayConfig> {
28        match self {
29            Self::Resolved(c) => Some(c),
30            Self::Spec(_) => {
31                tracing::error!(
32                    "gateway state is still Spec at runtime read; GatewayConfigSpec::resolve was \
33                     never called — treating gateway as absent"
34                );
35                None
36            },
37        }
38    }
39
40    #[must_use]
41    pub const fn as_spec_mut(&mut self) -> Option<&mut GatewayConfigSpec> {
42        match self {
43            Self::Spec(s) => Some(s),
44            Self::Resolved(_) => None,
45        }
46    }
47
48    pub fn into_spec(self) -> GatewayConfigSpec {
49        match self {
50            Self::Spec(s) => s,
51            Self::Resolved(c) => c.to_spec(),
52        }
53    }
54
55    #[must_use]
56    pub fn dispatchable_route_ids(&self, registry: &ProviderRegistry) -> Vec<RouteId> {
57        let config = match self {
58            Self::Resolved(c) => Cow::Borrowed(c),
59            Self::Spec(s) => Cow::Owned(s.clone().resolve()),
60        };
61        config.dispatchable_route_ids(registry)
62    }
63}
64
65impl<'de> Deserialize<'de> for GatewayState {
66    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
67    where
68        D: Deserializer<'de>,
69    {
70        GatewayConfigSpec::deserialize(deserializer).map(Self::Spec)
71    }
72}
73
74impl Serialize for GatewayState {
75    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
76    where
77        S: Serializer,
78    {
79        match self {
80            Self::Spec(s) => s.serialize(serializer),
81            Self::Resolved(c) => c.to_spec().serialize(serializer),
82        }
83    }
84}
85
86impl schemars::JsonSchema for GatewayState {
87    fn schema_name() -> Cow<'static, str> {
88        GatewayConfigSpec::schema_name()
89    }
90
91    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
92        GatewayConfigSpec::json_schema(generator)
93    }
94}