1use serde::{Deserialize, Serialize};
2use uptrakit_wire::surfaces::{self, SurfaceDescriptor};
3use uuid::Uuid;
4
5use crate::validation::{Validate, ValidationError};
6
7#[derive(Debug, Clone, Deserialize)]
9#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))]
10pub struct ListSurfacesQuery {
11 #[serde(default)]
13 pub slot: Option<String>,
14 #[serde(default)]
16 pub page: Option<String>,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
22pub struct SurfaceResponse {
23 #[serde(flatten)]
25 #[cfg_attr(feature = "openapi", schema(value_type = serde_json::Value))]
26 pub descriptor: SurfaceDescriptor,
27 pub provider_count: usize,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
33#[serde(rename_all = "snake_case")]
34pub enum SurfaceProviderAvailability {
35 Available,
36 Disconnected,
37 IncompatibleTenant,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
43pub struct SurfaceProviderInfo {
44 pub provider_id: String,
45 pub display_label: String,
46 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub service_id: Option<Uuid>,
48 pub availability: SurfaceProviderAvailability,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 #[cfg_attr(feature = "openapi", schema(value_type = Option<serde_json::Value>))]
51 pub encryption_metadata: Option<surfaces::ProviderEncryptionMetadata>,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
57pub struct SurfaceReadResponse {
58 #[cfg_attr(feature = "openapi", schema(value_type = serde_json::Value))]
60 pub descriptor: SurfaceDescriptor,
61 #[serde(default)]
63 #[cfg_attr(feature = "openapi", schema(value_type = Vec<serde_json::Value>))]
64 pub interactions: Vec<surfaces::InteractionDescriptor>,
65 #[serde(default)]
67 #[cfg_attr(feature = "openapi", schema(value_type = Vec<serde_json::Value>))]
68 pub data_sources: Vec<surfaces::DataSourceDescriptor>,
69}
70
71#[derive(Debug, Clone, Deserialize)]
81#[cfg_attr(
82 feature = "openapi",
83 derive(utoipa::IntoParams),
84 into_params(parameter_in = Query)
85)]
86pub struct ReadSurfaceInteractionQuery {
87 #[serde(default)]
89 pub target_provider_id: Option<String>,
90 #[serde(default)]
92 pub timeout_seconds: Option<u16>,
93 #[serde(default)]
95 pub page: Option<u64>,
96 #[serde(default)]
98 pub per_page: Option<u64>,
99}
100
101#[derive(Debug, Clone, Default, Serialize, Deserialize)]
103#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
104pub struct InvokeSurfaceInteractionRequest {
105 #[serde(default)]
107 #[cfg_attr(feature = "openapi", schema(value_type = serde_json::Value))]
108 pub params: serde_json::Map<String, serde_json::Value>,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 #[cfg_attr(feature = "openapi", schema(value_type = Option<serde_json::Value>))]
112 pub encrypted_sensitive_params: Option<surfaces::EncryptedSensitiveParams>,
113 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub target_provider_id: Option<String>,
115 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub idempotency_key: Option<String>,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
120 pub timeout_seconds: Option<u16>,
121}
122
123impl Validate for InvokeSurfaceInteractionRequest {
124 fn validate(&self) -> Result<(), ValidationError> {
125 Ok(())
127 }
128}
129
130impl crate::validation::sealed::Sealed for InvokeSurfaceInteractionRequest {}
131
132impl crate::validation::RoutingEnvelope for InvokeSurfaceInteractionRequest {
133 fn routing_envelope(&self) -> crate::validation::InvokeRoutingEnvelope {
134 crate::validation::InvokeRoutingEnvelope {
135 target_provider_id: self.target_provider_id.clone(),
136 timeout_seconds: self.timeout_seconds,
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144 use uptrakit_wire::limits::MAX_SURFACE_PARAMS_LEN;
145
146 #[test]
147 fn invoke_surface_interaction_request_validate_is_ok() {
148 InvokeSurfaceInteractionRequest::default()
149 .validate()
150 .expect("InvokeSurfaceInteractionRequest::default() should validate");
151 }
152
153 #[test]
154 fn invoke_request_validate_is_unconditionally_ok_canary() {
155 let base = InvokeSurfaceInteractionRequest {
175 params: serde_json::Map::from_iter([(
176 "k".to_string(),
177 serde_json::Value::String("v".to_string()),
178 )]),
179 encrypted_sensitive_params: None,
180 target_provider_id: Some("provider".to_string()),
181 idempotency_key: Some("key".to_string()),
182 timeout_seconds: Some(1),
183 };
184
185 let hostile_fixtures = [
186 InvokeSurfaceInteractionRequest {
187 target_provider_id: Some(String::new()),
188 ..base.clone()
189 },
190 InvokeSurfaceInteractionRequest {
191 target_provider_id: Some(" ".to_string()),
192 ..base.clone()
193 },
194 InvokeSurfaceInteractionRequest {
195 idempotency_key: Some(String::new()),
196 ..base.clone()
197 },
198 InvokeSurfaceInteractionRequest {
199 idempotency_key: Some(" ".to_string()),
200 ..base.clone()
201 },
202 InvokeSurfaceInteractionRequest {
203 timeout_seconds: Some(0),
204 ..base.clone()
205 },
206 InvokeSurfaceInteractionRequest {
207 params: serde_json::Map::from_iter([(
212 String::new(),
213 serde_json::Value::String("x".repeat(MAX_SURFACE_PARAMS_LEN + 1)),
214 )]),
215 ..base.clone()
216 },
217 ];
218
219 for fixture in hostile_fixtures {
220 fixture.validate().expect(
221 "validate() must stay unconditionally Ok until the discriminating test exists",
222 );
223 }
224 }
225
226 #[test]
227 fn routing_envelope_projects_only_the_envelope_fields() {
228 let req = InvokeSurfaceInteractionRequest {
229 target_provider_id: Some("p1".to_string()),
230 timeout_seconds: Some(30),
231 ..Default::default()
232 };
233 let env = crate::validation::RoutingEnvelope::routing_envelope(&req);
234 assert_eq!(env.target_provider_id.as_deref(), Some("p1"));
235 assert_eq!(env.timeout_seconds, Some(30));
236 }
237}