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
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use super::config::{GatewayConfig, GatewayConfigSpec};
11
12#[derive(Debug, Clone)]
13pub enum GatewayState {
14    Spec(GatewayConfigSpec),
15    Resolved(GatewayConfig),
16}
17
18impl GatewayState {
19    /// The resolved runtime config, or `None` if the loader did not run.
20    /// A `None` here is a bootstrap-ordering bug; the log line is the
21    /// signal — production read paths fall through to the same "gateway
22    /// absent" path they already handle.
23    #[must_use]
24    pub fn resolved(&self) -> Option<&GatewayConfig> {
25        match self {
26            Self::Resolved(c) => Some(c),
27            Self::Spec(_) => {
28                tracing::error!(
29                    "gateway state is still Spec at runtime read; GatewayConfigSpec::resolve was \
30                     never called — treating gateway as absent"
31                );
32                None
33            },
34        }
35    }
36
37    #[must_use]
38    pub const fn as_spec_mut(&mut self) -> Option<&mut GatewayConfigSpec> {
39        match self {
40            Self::Spec(s) => Some(s),
41            Self::Resolved(_) => None,
42        }
43    }
44
45    pub fn into_spec(self) -> GatewayConfigSpec {
46        match self {
47            Self::Spec(s) => s,
48            Self::Resolved(c) => c.to_spec(),
49        }
50    }
51}
52
53impl<'de> Deserialize<'de> for GatewayState {
54    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
55    where
56        D: Deserializer<'de>,
57    {
58        GatewayConfigSpec::deserialize(deserializer).map(Self::Spec)
59    }
60}
61
62impl Serialize for GatewayState {
63    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64    where
65        S: Serializer,
66    {
67        match self {
68            Self::Spec(s) => s.serialize(serializer),
69            Self::Resolved(c) => c.to_spec().serialize(serializer),
70        }
71    }
72}
73
74impl schemars::JsonSchema for GatewayState {
75    fn schema_name() -> std::borrow::Cow<'static, str> {
76        GatewayConfigSpec::schema_name()
77    }
78
79    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
80        GatewayConfigSpec::json_schema(generator)
81    }
82}