Skip to main content

uptrakit_surfaces/
data.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use thiserror::Error;
4
5use crate::{
6    ControllerQueryId, DataSourceId, IdentifierError, ProviderKind, validate_surface_identifier,
7};
8
9pub const MIN_PROVIDER_REFRESH_INTERVAL_SECONDS: u32 = 10;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case", tag = "kind")]
13pub enum DataSourceKind {
14    Static { data: Value },
15    ControllerQuery { query_id: ControllerQueryId },
16    ProviderQuery { operation_id: String },
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case", tag = "type")]
21pub enum RefreshPolicy {
22    Manual,
23    Interval { seconds: u32 },
24    Sse { topic: ControllerSseTopicId },
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
28#[serde(transparent)]
29pub struct ControllerSseTopicId(String);
30
31impl ControllerSseTopicId {
32    /// Constructs a validated SSE topic identifier.
33    ///
34    /// # Errors
35    /// Returns any [`IdentifierError`] from
36    /// [`validate_surface_identifier`] when `value` is not a valid
37    /// identifier.
38    pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
39        let value = value.into();
40        validate_surface_identifier(&value)?;
41        Ok(Self(value))
42    }
43
44    #[must_use]
45    pub const fn as_str(&self) -> &str {
46        self.0.as_str()
47    }
48}
49
50impl<'de> Deserialize<'de> for ControllerSseTopicId {
51    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
52    where
53        D: serde::Deserializer<'de>,
54    {
55        let value = String::deserialize(deserializer)?;
56        Self::new(value).map_err(serde::de::Error::custom)
57    }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "snake_case", tag = "type")]
62pub enum SchemaContract {
63    Any,
64    Object,
65    Array,
66    String,
67    Integer,
68    Number,
69    Boolean,
70    Null,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct DataSourceDescriptor {
75    pub data_source_id: DataSourceId,
76    pub kind: DataSourceKind,
77    pub result_schema: SchemaContract,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub pagination: Option<DataSourcePagination>,
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub sorting: Option<DataSourceSorting>,
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub filtering: Option<DataSourceFiltering>,
84    pub refresh_policy: RefreshPolicy,
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub empty_state: Option<DataSourceEmptyState>,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct DataSourcePagination {
91    pub default_page_size: u16,
92    pub max_page_size: u16,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96pub struct DataSourceSorting {
97    #[serde(default, skip_serializing_if = "Vec::is_empty")]
98    pub sortable_fields: Vec<String>,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub default_sort_field: Option<String>,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct DataSourceFiltering {
105    #[serde(default, skip_serializing_if = "Vec::is_empty")]
106    pub filter_fields: Vec<String>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub struct DataSourceEmptyState {
111    pub title: String,
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub description: Option<String>,
114}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
117pub enum DataSourceValidationError {
118    #[error("service providers cannot declare controller_query data sources")]
119    ServiceControllerQueryForbidden,
120    #[error(
121        "provider_query interval refresh must be at least {MIN_PROVIDER_REFRESH_INTERVAL_SECONDS} seconds"
122    )]
123    ProviderIntervalTooLow,
124}
125
126impl DataSourceDescriptor {
127    /// Validates provider-specific data source rules.
128    ///
129    /// # Errors
130    /// Returns
131    /// [`DataSourceValidationError::ServiceControllerQueryForbidden`]
132    /// when a service provider declares a `controller_query` data source.
133    /// Returns [`DataSourceValidationError::ProviderIntervalTooLow`] when
134    /// a `provider_query` data source uses interval refresh lower than
135    /// [`MIN_PROVIDER_REFRESH_INTERVAL_SECONDS`].
136    pub fn validate_for_provider(
137        &self,
138        provider_kind: ProviderKind,
139    ) -> Result<(), DataSourceValidationError> {
140        if provider_kind == ProviderKind::Service
141            && matches!(self.kind, DataSourceKind::ControllerQuery { .. })
142        {
143            return Err(DataSourceValidationError::ServiceControllerQueryForbidden);
144        }
145
146        if matches!(self.kind, DataSourceKind::ProviderQuery { .. })
147            && matches!(
148                self.refresh_policy,
149                RefreshPolicy::Interval { seconds }
150                    if seconds < MIN_PROVIDER_REFRESH_INTERVAL_SECONDS
151            )
152        {
153            return Err(DataSourceValidationError::ProviderIntervalTooLow);
154        }
155
156        Ok(())
157    }
158}