Skip to main content

phoxal_bundle/
participant.rs

1//! Final participant-instance records.
2
3use phoxal_model::identity::ComponentInstanceId;
4use phoxal_model::{Clock, Robot};
5use phoxal_runtime_contract::identity::{ParticipantArtifactId, ParticipantId};
6use phoxal_runtime_contract::metadata::ParticipantKind;
7use serde::{Deserialize, Serialize};
8
9use crate::{BinaryReference, DocumentError, ParticipantClock};
10
11/// One exact process entry in the final runtime graph.
12#[derive(phoxal_macros::DescribeWire, Clone, Debug, Deserialize, Serialize)]
13#[serde(deny_unknown_fields)]
14pub struct RuntimeParticipant {
15    pub(crate) id: ParticipantId,
16    pub(crate) artifact: ParticipantArtifactId,
17    pub(crate) config: Option<serde_json::Value>,
18    pub(crate) component: Option<ComponentInstanceId>,
19    pub(crate) clock: ParticipantClock,
20}
21
22impl RuntimeParticipant {
23    #[must_use]
24    pub fn new(
25        id: ParticipantId,
26        artifact: ParticipantArtifactId,
27        config: Option<serde_json::Value>,
28        component: Option<ComponentInstanceId>,
29        clock: ParticipantClock,
30    ) -> Self {
31        Self {
32            id,
33            artifact,
34            config,
35            component,
36            clock,
37        }
38    }
39    #[must_use]
40    pub const fn id(&self) -> &ParticipantId {
41        &self.id
42    }
43    #[must_use]
44    pub const fn artifact(&self) -> &ParticipantArtifactId {
45        &self.artifact
46    }
47    #[must_use]
48    pub fn config(&self) -> Option<&serde_json::Value> {
49        self.config.as_ref()
50    }
51    #[must_use]
52    pub const fn component(&self) -> Option<&ComponentInstanceId> {
53        self.component.as_ref()
54    }
55    #[must_use]
56    pub const fn clock(&self) -> ParticipantClock {
57        self.clock
58    }
59
60    pub(crate) fn validate(
61        &self,
62        robot: &Robot,
63        artifact: &BinaryReference,
64        config_validator: &jsonschema::Validator,
65    ) -> Result<(), DocumentError> {
66        if !artifact.path().starts_with_directory(crate::BIN_DIR) {
67            return Err(DocumentError::ArtifactOutsideBin {
68                artifact: self.artifact.clone(),
69                path: artifact.path().clone(),
70            });
71        }
72        if let Some(component) = &self.component
73            && robot.component_instance(component.as_str()).is_none()
74        {
75            return Err(DocumentError::UnknownComponent {
76                participant: self.id.clone(),
77                component_instance: component.clone(),
78            });
79        }
80        match (artifact.contract().kind, &self.component) {
81            (ParticipantKind::Driver, None) => {
82                return Err(DocumentError::MissingDriverComponent {
83                    participant: self.id.clone(),
84                });
85            }
86            (ParticipantKind::Driver, Some(_)) | (_, None) => {}
87            (kind, Some(component_instance)) => {
88                return Err(DocumentError::UnexpectedComponent {
89                    participant: self.id.clone(),
90                    kind,
91                    component_instance: component_instance.clone(),
92                });
93            }
94        }
95        let kind = artifact.contract().kind;
96        let execution_mode_matches = match kind {
97            ParticipantKind::Driver => {
98                robot.clock() == Clock::Real && self.clock != ParticipantClock::Simulation
99            }
100            ParticipantKind::Simulator => {
101                robot.clock() == Clock::Simulated && self.clock == ParticipantClock::Simulation
102            }
103            ParticipantKind::Brain | ParticipantKind::Service => true,
104        };
105        if !execution_mode_matches {
106            return Err(DocumentError::ExecutionModeMismatch {
107                participant: self.id.clone(),
108                kind,
109                robot: robot.clock(),
110                participant_clock: self.clock,
111            });
112        }
113        match (robot.clock(), self.clock) {
114            (Clock::Real, ParticipantClock::Simulation) => {
115                return Err(DocumentError::ClockMismatch {
116                    participant: self.id.clone(),
117                    robot: Clock::Real,
118                    participant_clock: self.clock,
119                });
120            }
121            (Clock::Simulated, ParticipantClock::Real) => {
122                return Err(DocumentError::ClockMismatch {
123                    participant: self.id.clone(),
124                    robot: Clock::Simulated,
125                    participant_clock: self.clock,
126                });
127            }
128            _ => {}
129        }
130        let null = serde_json::Value::Null;
131        let config = self.config.as_ref().unwrap_or(&null);
132        if let Err(error) = config_validator.validate(config) {
133            return Err(DocumentError::InvalidConfig {
134                participant: self.id.clone(),
135                error: error.to_string(),
136            });
137        }
138        Ok(())
139    }
140}