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`] once a
5//! `profile_dir` is available. Runtime read paths must observe
6//! [`GatewayState::Resolved`] — they consult [`Self::resolved`] which
7//! logs and returns `None` if the loader has not run.
8
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10
11use super::config::{GatewayConfig, GatewayConfigSpec};
12
13#[derive(Debug, Clone)]
14pub enum GatewayState {
15    /// Freshly parsed YAML; catalog path (if any) has not been read.
16    Spec(GatewayConfigSpec),
17    /// Loader has run; any catalog reference has been read and validated.
18    Resolved(GatewayConfig),
19}
20
21impl GatewayState {
22    /// The resolved runtime config, or `None` if the loader did not run.
23    /// A `None` here is a bootstrap-ordering bug; the log line is the
24    /// signal — production read paths fall through to the same "gateway
25    /// absent" path they already handle.
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
56impl<'de> Deserialize<'de> for GatewayState {
57    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
58    where
59        D: Deserializer<'de>,
60    {
61        GatewayConfigSpec::deserialize(deserializer).map(Self::Spec)
62    }
63}
64
65impl Serialize for GatewayState {
66    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
67    where
68        S: Serializer,
69    {
70        match self {
71            Self::Spec(s) => s.serialize(serializer),
72            Self::Resolved(c) => c.to_spec().serialize(serializer),
73        }
74    }
75}
76
77impl schemars::JsonSchema for GatewayState {
78    fn schema_name() -> std::borrow::Cow<'static, str> {
79        GatewayConfigSpec::schema_name()
80    }
81
82    fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
83        GatewayConfigSpec::json_schema(generator)
84    }
85}