1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3use std::collections::HashSet;
4use thiserror::Error;
5use uptrakit_shared_macros::wire_safe_enum;
6use uuid::Uuid;
7
8use crate::{
9 Capability, CapabilitySet, DataSourceDescriptor, DataSourceKind, FrameworkGeneration,
10 FrameworkGenerationRange, InteractionDescriptor, InteractionId, InteractionKind,
11 InteractionTransport, ProviderKind, Scope, SlotValidationError, SurfaceDescriptor, SurfaceId,
12 SurfaceNode, SurfaceSlotDef, Targeting, validate_slot_id, validate_surface_identifier,
13};
14
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct SurfaceRegistration {
17 pub provider: ProviderIdentity,
18 pub framework_generation: FrameworkGeneration,
19 pub capabilities: CapabilitySet,
20 pub effective_tenant_binding: EffectiveTenantBinding,
21 #[serde(default, skip_serializing_if = "Vec::is_empty")]
22 pub surfaces: Vec<RegisteredSurface>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
24 pub encryption_metadata: Option<ProviderEncryptionMetadata>,
25}
26
27impl SurfaceRegistration {
28 pub fn validate_against(
37 &self,
38 policy: &SurfaceRegistrationPolicy,
39 ) -> Result<(), SurfaceRegistrationError> {
40 validate_supported_generation(self, policy)?;
41 validate_required_registration_capabilities(self, policy)?;
42 validate_registered_surfaces(self)?;
43 Ok(())
44 }
45}
46
47fn validate_supported_generation(
48 registration: &SurfaceRegistration,
49 policy: &SurfaceRegistrationPolicy,
50) -> Result<(), SurfaceRegistrationError> {
51 if policy
52 .supported_generation
53 .includes(registration.framework_generation)
54 {
55 return Ok(());
56 }
57
58 Err(registration_error(
59 SurfaceRegistrationErrorCode::UnsupportedGeneration,
60 format!(
61 "framework generation {}.{} is outside supported range {}.{}..={}.{}",
62 registration.framework_generation.major,
63 registration.framework_generation.minor,
64 policy.supported_generation.min.major,
65 policy.supported_generation.min.minor,
66 policy.supported_generation.max.major,
67 policy.supported_generation.max.minor,
68 ),
69 ))
70}
71
72fn validate_required_registration_capabilities(
73 registration: &SurfaceRegistration,
74 policy: &SurfaceRegistrationPolicy,
75) -> Result<(), SurfaceRegistrationError> {
76 if registration
77 .capabilities
78 .contains_all(&policy.required_capabilities)
79 {
80 return Ok(());
81 }
82
83 Err(missing_capability(
84 "registration is missing one or more required capabilities",
85 ))
86}
87
88fn validate_registered_surfaces(
89 registration: &SurfaceRegistration,
90) -> Result<(), SurfaceRegistrationError> {
91 RegisteredSurfacesValidator::new(registration).validate()
92}
93
94struct RegisteredSurfacesValidator<'a> {
95 provider_kind: ProviderKind,
96 capabilities: &'a CapabilitySet,
97 surfaces: &'a [RegisteredSurface],
98 surface_ids: HashSet<&'a str>,
99 single_entry_slots: HashSet<&'static str>,
100}
101
102impl<'a> RegisteredSurfacesValidator<'a> {
103 fn new(registration: &'a SurfaceRegistration) -> Self {
104 Self {
105 provider_kind: registration.provider.provider_kind,
106 capabilities: ®istration.capabilities,
107 surfaces: ®istration.surfaces,
108 surface_ids: HashSet::new(),
109 single_entry_slots: HashSet::new(),
110 }
111 }
112
113 fn validate(mut self) -> Result<(), SurfaceRegistrationError> {
114 for surface in self.surfaces {
115 self.validate_surface(surface)?;
116 }
117
118 Ok(())
119 }
120
121 fn validate_surface(
122 &mut self,
123 surface: &'a RegisteredSurface,
124 ) -> Result<(), SurfaceRegistrationError> {
125 validate_surface_descriptor_rules(
126 surface,
127 self.provider_kind,
128 self.capabilities,
129 &mut self.surface_ids,
130 &mut self.single_entry_slots,
131 )?;
132
133 let interaction_ids = validate_surface_interaction_rules(surface)?;
134 validate_workflow_step_references(
135 &surface.descriptor.surface_id,
136 &surface.interactions,
137 &interaction_ids,
138 )?;
139
140 let data_source_ids = validate_surface_data_source_rules(surface)?;
141 validate_root_node_references(
142 &surface.descriptor.surface_id,
143 &surface.descriptor.root_node,
144 &interaction_ids,
145 &data_source_ids,
146 )
147 }
148}
149
150fn validate_surface_descriptor_rules<'a>(
151 surface: &'a RegisteredSurface,
152 registration_provider_kind: ProviderKind,
153 capabilities: &CapabilitySet,
154 surface_ids: &mut HashSet<&'a str>,
155 single_entry_slots: &mut HashSet<&'static str>,
156) -> Result<(), SurfaceRegistrationError> {
157 validate_unique_surface_id(surface, surface_ids)?;
158 validate_surface_provider_kind(surface, registration_provider_kind)?;
159 let slot_def = validate_surface_slot(surface)?;
160 validate_single_entry_slot_occupancy(slot_def, single_entry_slots)?;
161 validate_surface_priority_range(surface, slot_def)?;
162 validate_surface_required_capabilities(surface, capabilities)?;
163 validate_surface_usage_capabilities(
164 &surface.descriptor.surface_id,
165 capabilities,
166 &surface.descriptor.root_node,
167 &surface.descriptor.targeting,
168 &surface.interactions,
169 &surface.data_sources,
170 )
171}
172
173fn validate_unique_surface_id<'a>(
174 surface: &'a RegisteredSurface,
175 surface_ids: &mut HashSet<&'a str>,
176) -> Result<(), SurfaceRegistrationError> {
177 if surface_ids.insert(surface.descriptor.surface_id.as_str()) {
178 return Ok(());
179 }
180
181 Err(invalid_contract(format!(
182 "duplicate surface_id `{}` within registration batch",
183 surface.descriptor.surface_id
184 )))
185}
186
187fn validate_surface_provider_kind(
188 surface: &RegisteredSurface,
189 registration_provider_kind: ProviderKind,
190) -> Result<(), SurfaceRegistrationError> {
191 if surface.descriptor.provider_kind == registration_provider_kind {
192 return Ok(());
193 }
194
195 Err(invalid_contract(format!(
196 "surface `{}` provider_kind does not match registration provider_kind",
197 surface.descriptor.surface_id
198 )))
199}
200
201fn validate_surface_slot(
202 surface: &RegisteredSurface,
203) -> Result<&'static SurfaceSlotDef, SurfaceRegistrationError> {
204 validate_slot_id(&surface.descriptor.slot).map_err(map_slot_validation_error)
205}
206
207fn validate_single_entry_slot_occupancy(
208 slot_def: &'static SurfaceSlotDef,
209 single_entry_slots: &mut HashSet<&'static str>,
210) -> Result<(), SurfaceRegistrationError> {
211 if slot_def.multi_entry || single_entry_slots.insert(slot_def.id) {
212 return Ok(());
213 }
214
215 Err(invalid_contract(format!(
216 "slot `{}` is single-entry and cannot accept multiple surfaces in one registration batch",
217 slot_def.id
218 )))
219}
220
221fn validate_surface_priority_range(
222 surface: &RegisteredSurface,
223 slot_def: &'static SurfaceSlotDef,
224) -> Result<(), SurfaceRegistrationError> {
225 if surface.descriptor.provider_kind == ProviderKind::BuiltIn {
226 return Ok(());
227 }
228
229 if surface.descriptor.priority >= slot_def.provider_priority_min
230 && surface.descriptor.priority <= slot_def.provider_priority_max
231 {
232 return Ok(());
233 }
234
235 Err(invalid_contract(format!(
236 "surface `{}` priority {} is outside slot `{}` provider range {}..={}",
237 surface.descriptor.surface_id,
238 surface.descriptor.priority,
239 slot_def.id,
240 slot_def.provider_priority_min,
241 slot_def.provider_priority_max
242 )))
243}
244
245fn validate_surface_required_capabilities(
246 surface: &RegisteredSurface,
247 capabilities: &CapabilitySet,
248) -> Result<(), SurfaceRegistrationError> {
249 if capabilities.contains_all(&surface.descriptor.required_capabilities) {
250 return Ok(());
251 }
252
253 Err(missing_capability(format!(
254 "surface `{}` requires capabilities not advertised by registration",
255 surface.descriptor.surface_id
256 )))
257}
258
259fn validate_surface_interaction_rules(
260 surface: &RegisteredSurface,
261) -> Result<HashSet<&str>, SurfaceRegistrationError> {
262 let mut interaction_ids: HashSet<&str> = HashSet::new();
263 for interaction in &surface.interactions {
264 validate_unique_interaction_id(surface, interaction, &mut interaction_ids)?;
265 validate_interaction_provider_rules(surface, interaction)?;
266 }
267
268 Ok(interaction_ids)
269}
270
271fn validate_unique_interaction_id<'a>(
272 surface: &'a RegisteredSurface,
273 interaction: &'a InteractionDescriptor,
274 interaction_ids: &mut HashSet<&'a str>,
275) -> Result<(), SurfaceRegistrationError> {
276 if interaction_ids.insert(interaction.interaction_id.as_str()) {
277 return Ok(());
278 }
279
280 Err(invalid_contract(format!(
281 "duplicate interaction_id `{}` within surface `{}`",
282 interaction.interaction_id, surface.descriptor.surface_id
283 )))
284}
285
286fn validate_interaction_provider_rules(
287 surface: &RegisteredSurface,
288 interaction: &InteractionDescriptor,
289) -> Result<(), SurfaceRegistrationError> {
290 interaction
291 .validate_for_provider(surface.descriptor.provider_kind)
292 .map_err(|err| invalid_contract(err.to_string()))
293}
294
295fn validate_surface_data_source_rules(
296 surface: &RegisteredSurface,
297) -> Result<HashSet<&str>, SurfaceRegistrationError> {
298 let mut data_source_ids: HashSet<&str> = HashSet::new();
299 for data_source in &surface.data_sources {
300 validate_unique_data_source_id(surface, data_source, &mut data_source_ids)?;
301 validate_data_source_provider_rules(surface, data_source)?;
302 }
303
304 Ok(data_source_ids)
305}
306
307fn validate_unique_data_source_id<'a>(
308 surface: &'a RegisteredSurface,
309 data_source: &'a DataSourceDescriptor,
310 data_source_ids: &mut HashSet<&'a str>,
311) -> Result<(), SurfaceRegistrationError> {
312 if data_source_ids.insert(data_source.data_source_id.as_str()) {
313 return Ok(());
314 }
315
316 Err(invalid_contract(format!(
317 "duplicate data_source_id `{}` within surface `{}`",
318 data_source.data_source_id, surface.descriptor.surface_id
319 )))
320}
321
322fn validate_data_source_provider_rules(
323 surface: &RegisteredSurface,
324 data_source: &DataSourceDescriptor,
325) -> Result<(), SurfaceRegistrationError> {
326 data_source
327 .validate_for_provider(surface.descriptor.provider_kind)
328 .map_err(|err| invalid_contract(err.to_string()))
329}
330
331fn map_slot_validation_error(err: SlotValidationError) -> SurfaceRegistrationError {
332 let code = match err {
333 SlotValidationError::UnknownSlot(_) => SurfaceRegistrationErrorCode::InvalidSlot,
334 SlotValidationError::InvalidIdentifier(_) => SurfaceRegistrationErrorCode::InvalidContract,
335 };
336
337 registration_error(code, err.to_string())
338}
339
340fn registration_error(
341 code: SurfaceRegistrationErrorCode,
342 message: impl Into<String>,
343) -> SurfaceRegistrationError {
344 SurfaceRegistrationError::new(code, message.into())
345}
346
347fn invalid_contract(message: impl Into<String>) -> SurfaceRegistrationError {
348 registration_error(SurfaceRegistrationErrorCode::InvalidContract, message)
349}
350
351fn missing_capability(message: impl Into<String>) -> SurfaceRegistrationError {
352 registration_error(SurfaceRegistrationErrorCode::MissingCapability, message)
353}
354
355fn validate_surface_usage_capabilities(
356 surface_id: &SurfaceId,
357 capabilities: &CapabilitySet,
358 root_node: &SurfaceNode,
359 targeting: &Targeting,
360 interactions: &[InteractionDescriptor],
361 data_sources: &[DataSourceDescriptor],
362) -> Result<(), SurfaceRegistrationError> {
363 validate_node_capabilities(surface_id, capabilities, root_node)?;
364
365 let targeting_capability = match targeting {
366 Targeting::Universal => Capability::UniversalTargeting,
367 Targeting::Targeted => Capability::TargetedTargeting,
368 };
369 require_capability(
370 capabilities,
371 targeting_capability,
372 surface_id,
373 "targeting mode",
374 )?;
375
376 for interaction in interactions {
377 let kind_capability = match interaction.kind {
378 InteractionKind::MutationAction => Capability::MutationAction,
379 InteractionKind::FormSubmit => Capability::FormSubmit,
380 InteractionKind::Workflow => Capability::Workflow,
381 InteractionKind::Navigate => Capability::Navigate,
382 InteractionKind::DataLoad => Capability::DataLoad,
383 InteractionKind::ConfirmableAction => Capability::ConfirmableAction,
384 };
385 require_capability(
386 capabilities,
387 kind_capability,
388 surface_id,
389 "interaction kind",
390 )?;
391
392 if matches!(
393 &interaction.transport,
394 InteractionTransport::ProviderProxied
395 ) {
396 require_capability(
397 capabilities,
398 Capability::ProviderInitiatedActions,
399 surface_id,
400 "interaction transport",
401 )?;
402 }
403
404 if !interaction.sensitive_fields.is_empty() {
405 require_capability(
406 capabilities,
407 Capability::SensitiveFields,
408 surface_id,
409 "sensitive fields",
410 )?;
411 }
412 }
413
414 for data_source in data_sources {
415 let kind_capability = match &data_source.kind {
416 DataSourceKind::Static { .. } => Capability::StaticDataSource,
417 DataSourceKind::ControllerQuery { .. } => Capability::ControllerQueryDataSource,
418 DataSourceKind::ProviderQuery { .. } => Capability::ProviderQueryDataSource,
419 };
420 require_capability(
421 capabilities,
422 kind_capability,
423 surface_id,
424 "data source kind",
425 )?;
426 }
427
428 Ok(())
429}
430
431fn validate_node_capabilities(
432 surface_id: &SurfaceId,
433 capabilities: &CapabilitySet,
434 node: &SurfaceNode,
435) -> Result<(), SurfaceRegistrationError> {
436 let node_capability = match node {
437 SurfaceNode::Section { children, .. } => {
438 for child in children {
439 validate_node_capabilities(surface_id, capabilities, child)?;
440 }
441 Capability::SectionNode
442 }
443 SurfaceNode::TextBlock { .. } => Capability::TextBlockNode,
444 SurfaceNode::KeyValue { .. } => Capability::KeyValueNode,
445 SurfaceNode::Table { .. } => Capability::TableNode,
446 SurfaceNode::Form { .. } => Capability::FormNode,
447 SurfaceNode::ActionBar { .. } => Capability::ActionBarNode,
448 SurfaceNode::Tabs { tabs } => {
449 for tab in tabs {
450 validate_node_capabilities(surface_id, capabilities, &tab.root)?;
451 }
452 Capability::TabsNode
453 }
454 SurfaceNode::Callout { .. } => Capability::CalloutNode,
455 SurfaceNode::EmptyState { .. } => Capability::EmptyStateNode,
456 SurfaceNode::ModalTrigger { modal_nodes, .. } => {
457 for child in modal_nodes {
458 validate_node_capabilities(surface_id, capabilities, child)?;
459 }
460 Capability::ModalTriggerNode
461 }
462 SurfaceNode::WorkflowTrigger { step_nodes, .. } => {
463 for child in step_nodes {
464 validate_node_capabilities(surface_id, capabilities, child)?;
465 }
466 Capability::WorkflowTriggerNode
467 }
468 };
469
470 require_capability(capabilities, node_capability, surface_id, "root_node kind")
471}
472
473fn require_capability(
474 capabilities: &CapabilitySet,
475 required: Capability,
476 surface_id: &SurfaceId,
477 usage: &str,
478) -> Result<(), SurfaceRegistrationError> {
479 if capabilities.0.contains(&required) {
480 return Ok(());
481 }
482
483 Err(missing_capability(format!(
484 "surface `{}` uses {} that requires capability `{}`",
485 surface_id,
486 usage,
487 serde_json::to_string(&required)
488 .unwrap_or_else(|_| "\"unknown\"".to_owned())
489 .trim_matches('"')
490 )))
491}
492
493fn validate_root_node_references(
494 surface_id: &SurfaceId,
495 node: &SurfaceNode,
496 interaction_ids: &HashSet<&str>,
497 data_source_ids: &HashSet<&str>,
498) -> Result<(), SurfaceRegistrationError> {
499 RootNodeReferenceValidator::new(surface_id, interaction_ids, data_source_ids).validate(node)
500}
501
502struct RootNodeReferenceValidator<'a> {
503 surface_id: &'a SurfaceId,
504 interaction_ids: &'a HashSet<&'a str>,
505 data_source_ids: &'a HashSet<&'a str>,
506}
507
508impl<'a> RootNodeReferenceValidator<'a> {
509 fn new(
510 surface_id: &'a SurfaceId,
511 interaction_ids: &'a HashSet<&'a str>,
512 data_source_ids: &'a HashSet<&'a str>,
513 ) -> Self {
514 Self {
515 surface_id,
516 interaction_ids,
517 data_source_ids,
518 }
519 }
520
521 fn validate(&self, node: &SurfaceNode) -> Result<(), SurfaceRegistrationError> {
522 match node {
523 SurfaceNode::Section { children, .. } => self.validate_children(children),
524 SurfaceNode::TextBlock { .. } => Ok(()),
525 SurfaceNode::KeyValue { data_source_id } => {
526 self.require_data_source_reference(data_source_id.as_str())
527 }
528 SurfaceNode::Table {
529 data_source_id,
530 row_actions,
531 ..
532 } => {
533 self.require_data_source_reference(data_source_id.as_str())?;
534 self.validate_table_row_actions(row_actions)
535 }
536 SurfaceNode::Form { interaction_id } => {
537 self.require_root_interaction_reference(interaction_id.as_str())
538 }
539 SurfaceNode::ActionBar { action_ids } => self.validate_action_bar(action_ids),
540 SurfaceNode::Tabs { tabs } => self.validate_tabs(tabs),
541 SurfaceNode::Callout { .. } | SurfaceNode::EmptyState { .. } => Ok(()),
542 SurfaceNode::ModalTrigger {
543 interaction_id,
544 modal_nodes,
545 } => {
546 self.require_root_interaction_reference(interaction_id.as_str())?;
547 self.validate_children(modal_nodes)
548 }
549 SurfaceNode::WorkflowTrigger {
550 interaction_id,
551 step_nodes,
552 } => {
553 self.require_root_interaction_reference(interaction_id.as_str())?;
554 self.validate_children(step_nodes)
555 }
556 }
557 }
558
559 fn validate_children(&self, nodes: &[SurfaceNode]) -> Result<(), SurfaceRegistrationError> {
560 for child in nodes {
561 self.validate(child)?;
562 }
563 Ok(())
564 }
565
566 fn validate_table_row_actions(
567 &self,
568 row_actions: &[crate::SurfaceTableRowAction],
569 ) -> Result<(), SurfaceRegistrationError> {
570 for row_action in row_actions {
571 self.require_interaction_reference(row_action.interaction_id.as_str(), || {
572 format!(
573 "surface `{}` table references unknown row-action interaction_id `{}`",
574 self.surface_id, row_action.interaction_id
575 )
576 })?;
577 }
578 Ok(())
579 }
580
581 fn validate_action_bar(
582 &self,
583 action_ids: &[InteractionId],
584 ) -> Result<(), SurfaceRegistrationError> {
585 for action_id in action_ids {
586 self.require_root_interaction_reference(action_id.as_str())?;
587 }
588 Ok(())
589 }
590
591 fn validate_tabs(&self, tabs: &[crate::SurfaceTab]) -> Result<(), SurfaceRegistrationError> {
592 let mut tab_ids: HashSet<&str> = HashSet::new();
593
594 for tab in tabs {
595 validate_surface_identifier(tab.id.as_str()).map_err(|err| {
596 invalid_contract(format!(
597 "surface `{}` root_node contains invalid tab id `{}`: {}",
598 self.surface_id, tab.id, err
599 ))
600 })?;
601
602 if !tab_ids.insert(tab.id.as_str()) {
603 return Err(invalid_contract(format!(
604 "surface `{}` root_node contains duplicate tab id `{}` within one tabs node",
605 self.surface_id, tab.id
606 )));
607 }
608
609 self.validate(&tab.root)?;
610 }
611
612 Ok(())
613 }
614
615 fn require_root_interaction_reference(
616 &self,
617 interaction_id: &str,
618 ) -> Result<(), SurfaceRegistrationError> {
619 self.require_interaction_reference(interaction_id, || {
620 format!(
621 "surface `{}` root_node references unknown interaction_id `{}`",
622 self.surface_id, interaction_id
623 )
624 })
625 }
626
627 fn require_data_source_reference(
628 &self,
629 data_source_id: &str,
630 ) -> Result<(), SurfaceRegistrationError> {
631 ensure_known_reference(self.data_source_ids, data_source_id, || {
632 format!(
633 "surface `{}` root_node references unknown data_source_id `{}`",
634 self.surface_id, data_source_id
635 )
636 })
637 }
638
639 fn require_interaction_reference(
640 &self,
641 interaction_id: &str,
642 error_message: impl FnOnce() -> String,
643 ) -> Result<(), SurfaceRegistrationError> {
644 ensure_known_reference(self.interaction_ids, interaction_id, error_message)
645 }
646}
647
648fn validate_workflow_step_references(
649 surface_id: &SurfaceId,
650 interactions: &[InteractionDescriptor],
651 interaction_ids: &HashSet<&str>,
652) -> Result<(), SurfaceRegistrationError> {
653 WorkflowStepReferenceValidator::new(surface_id, interaction_ids).validate(interactions)
654}
655
656struct WorkflowStepReferenceValidator<'a> {
657 surface_id: &'a SurfaceId,
658 interaction_ids: &'a HashSet<&'a str>,
659}
660
661impl<'a> WorkflowStepReferenceValidator<'a> {
662 fn new(surface_id: &'a SurfaceId, interaction_ids: &'a HashSet<&'a str>) -> Self {
663 Self {
664 surface_id,
665 interaction_ids,
666 }
667 }
668
669 fn validate(
670 &self,
671 interactions: &[InteractionDescriptor],
672 ) -> Result<(), SurfaceRegistrationError> {
673 for interaction in interactions {
674 if interaction.kind != InteractionKind::Workflow {
675 continue;
676 }
677 self.validate_workflow_interaction_steps(interaction)?;
678 }
679
680 Ok(())
681 }
682
683 fn validate_workflow_interaction_steps(
684 &self,
685 interaction: &InteractionDescriptor,
686 ) -> Result<(), SurfaceRegistrationError> {
687 for step in &interaction.workflow_steps {
688 if let Some(submit_interaction_id) = &step.submit_interaction_id {
689 ensure_known_reference(
690 self.interaction_ids,
691 submit_interaction_id.as_str(),
692 || {
693 format!(
694 "surface `{}` workflow interaction `{}` references unknown submit_interaction_id `{}` in step `{}`",
695 self.surface_id,
696 interaction.interaction_id,
697 submit_interaction_id,
698 step.step_id
699 )
700 },
701 )?;
702 }
703
704 if let Some(form_ui) = &step.form_ui
705 && let Some(pre_load_interaction_id) = &form_ui.pre_load_interaction_id
706 {
707 ensure_known_reference(
708 self.interaction_ids,
709 pre_load_interaction_id.as_str(),
710 || {
711 format!(
712 "surface `{}` workflow interaction `{}` references unknown pre_load_interaction_id `{}` in step `{}`",
713 self.surface_id,
714 interaction.interaction_id,
715 pre_load_interaction_id,
716 step.step_id
717 )
718 },
719 )?;
720 }
721 }
722
723 Ok(())
724 }
725}
726
727fn ensure_known_reference(
728 known_ids: &HashSet<&str>,
729 reference_id: &str,
730 error_message: impl FnOnce() -> String,
731) -> Result<(), SurfaceRegistrationError> {
732 if known_ids.contains(reference_id) {
733 return Ok(());
734 }
735
736 Err(invalid_contract(error_message()))
737}
738
739#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
740pub struct ProviderIdentity {
741 pub provider_id: String,
742 pub provider_kind: ProviderKind,
743 pub provider_namespace: String,
744}
745
746#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
747pub struct EffectiveTenantBinding {
748 pub scope: Scope,
749 #[serde(default, skip_serializing_if = "Option::is_none")]
750 pub tenant_id: Option<String>,
751}
752
753#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
754pub struct ProviderEncryptionMetadata {
755 pub key_id: String,
756 pub algorithm: ProviderEncryptionAlgorithm,
757 pub public_key: String,
758}
759
760wire_safe_enum! {
761 #[derive(Debug, Clone, PartialEq, Eq)]
763 pub enum ProviderEncryptionAlgorithm {
764 EciesP256 => "ecies_p256",
765 }
766 parse_error = ParseProviderEncryptionAlgorithmError("invalid provider encryption algorithm");
767}
768
769impl ProviderEncryptionAlgorithm {
770 pub const KNOWN_VARIANTS: &'static [ProviderEncryptionAlgorithm] =
773 &[ProviderEncryptionAlgorithm::EciesP256];
774}
775
776#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
777pub struct RegisteredSurface {
778 pub descriptor: SurfaceDescriptor,
779 #[serde(default, skip_serializing_if = "Vec::is_empty")]
780 pub interactions: Vec<InteractionDescriptor>,
781 #[serde(default, skip_serializing_if = "Vec::is_empty")]
782 pub data_sources: Vec<DataSourceDescriptor>,
783}
784
785#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
786pub struct SurfaceRegistrationPolicy {
787 pub supported_generation: FrameworkGenerationRange,
788 pub required_capabilities: CapabilitySet,
789}
790
791#[derive(Debug, Clone, PartialEq, Eq, Error)]
792#[error("{code:?}: {message}")]
793pub struct SurfaceRegistrationError {
794 pub code: SurfaceRegistrationErrorCode,
795 pub message: String,
796}
797
798impl SurfaceRegistrationError {
799 pub fn new(code: SurfaceRegistrationErrorCode, message: String) -> Self {
800 Self { code, message }
801 }
802}
803
804#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
805#[serde(rename_all = "snake_case")]
806pub enum SurfaceRegistrationErrorCode {
807 UnsupportedGeneration,
808 MissingCapability,
809 InvalidSlot,
810 InvalidContract,
811}
812
813#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
814pub struct SurfaceActionRequest {
815 pub request_id: Uuid,
816 pub tenant_id: String,
817 pub surface_id: SurfaceId,
818 pub interaction_id: InteractionId,
819 pub idempotency_key: String,
820 #[serde(default, skip_serializing_if = "Option::is_none")]
821 pub target_provider_id: Option<String>,
822 pub caller_origin: CallerOrigin,
823 #[serde(default, skip_serializing_if = "Map::is_empty")]
824 pub params: Map<String, Value>,
825 #[serde(default, skip_serializing_if = "Option::is_none")]
826 pub encrypted_sensitive_params: Option<EncryptedSensitiveParams>,
827}
828
829#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
830#[serde(rename_all = "snake_case", tag = "kind")]
831pub enum CallerOrigin {
832 UserSession { user_id: String, session_id: String },
833 BuiltInSystem { principal: String },
834 Provider { provider_id: String },
835}
836
837#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
838pub struct EncryptedSensitiveParams {
839 pub key_id: String,
840 pub algorithm: ProviderEncryptionAlgorithm,
841 pub ciphertext_b64: String,
842}
843
844#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
845pub struct SurfaceActionCancel {
846 pub request_id: Uuid,
847 pub target_provider_id: String,
848 pub reason: SurfaceActionCancelReason,
849}
850
851#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
852#[serde(rename_all = "snake_case")]
853pub enum SurfaceActionCancelReason {
854 Timeout,
855 RequestCancelled,
856 ProviderDisconnected,
857}
858
859#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
860pub struct SurfaceActionResponse {
861 pub request_id: Uuid,
862 pub success: bool,
863 #[serde(default, skip_serializing_if = "Option::is_none")]
864 pub result: Option<Value>,
865 #[serde(default, skip_serializing_if = "Option::is_none")]
866 pub error: Option<SurfaceActionError>,
867}
868
869#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
870pub struct SurfaceActionError {
871 pub code: SurfaceActionErrorCode,
872 pub message: String,
873 #[serde(default, skip_serializing_if = "Option::is_none")]
874 pub details: Option<Value>,
875}
876
877#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
878#[serde(rename_all = "snake_case")]
879pub enum SurfaceActionErrorCode {
880 PermissionDenied,
881 InvalidRequest,
882 SchemaValidationFailed,
883 UnsupportedCapability,
884 ProviderUnavailable,
885 Timeout,
886 DuplicateRequest,
887 InternalError,
888}