1use std::path::PathBuf;
4
5use phoxal_model::component::capability::MotorCommand;
6use phoxal_model::identity::{CapabilityRef, ComponentInstanceId};
7use phoxal_model::{AssetId, Clock};
8use phoxal_runtime_contract::identity::{ParticipantArtifactId, ParticipantId};
9use phoxal_runtime_contract::metadata::ParticipantKind;
10use phoxal_runtime_contract::metadata::ParticipantRequirement;
11use phoxal_runtime_contract::version::FrameworkVersion;
12
13use crate::{BundlePath, BundlePathError, DigestError, ParticipantClock, Sha256Digest};
14
15#[derive(Clone, Debug, thiserror::Error)]
17pub enum DocumentError {
18 #[error(
19 "runtime graph has {count} participants; limit is {}",
20 crate::MAX_RUNTIME_PARTICIPANTS
21 )]
22 TooManyParticipants { count: usize },
23 #[error(
27 "artifact '{artifact}' was built from framework {actual}, but this runtime is on the {} \
28 line (from framework {expected})",
29 expected.compatibility_line()
30 )]
31 MixedFrameworkLine {
32 artifact: ParticipantArtifactId,
33 expected: FrameworkVersion,
34 actual: FrameworkVersion,
35 },
36 #[error("duplicate participant id '{id}'")]
37 DuplicateParticipant { id: ParticipantId },
38 #[error("duplicate participant binary path '{path}'")]
39 DuplicateBinary { path: BundlePath },
40 #[error("participant '{participant}' references unknown artifact '{artifact}'")]
41 UnknownArtifact {
42 participant: ParticipantId,
43 artifact: ParticipantArtifactId,
44 },
45 #[error("artifact '{artifact}' contract names '{contract}'")]
46 ArtifactContractMismatch {
47 artifact: ParticipantArtifactId,
48 contract: ParticipantArtifactId,
49 },
50 #[error("artifact '{artifact}' path is outside bin/: {path}")]
51 ArtifactOutsideBin {
52 artifact: ParticipantArtifactId,
53 path: BundlePath,
54 },
55 #[error("brain artifact id is '{actual}', expected 'brain'")]
56 BrainArtifactId { actual: ParticipantArtifactId },
57 #[error("brain artifact path is '{actual}', expected 'bin/brain'")]
58 BrainArtifactPath { actual: BundlePath },
59 #[error("artifact '{artifact}' is not selected by any runtime participant")]
60 UnusedArtifact { artifact: ParticipantArtifactId },
61 #[error("participant '{participant}' names unknown component instance '{component_instance}'")]
62 UnknownComponent {
63 participant: ParticipantId,
64 component_instance: ComponentInstanceId,
65 },
66 #[error("driver participant '{participant}' has no component binding")]
67 MissingDriverComponent { participant: ParticipantId },
68 #[error(
69 "{kind:?} participant '{participant}' cannot bind component instance '{component_instance}'"
70 )]
71 UnexpectedComponent {
72 participant: ParticipantId,
73 kind: ParticipantKind,
74 component_instance: ComponentInstanceId,
75 },
76 #[error("runtime graph has no mandatory 'brain' participant")]
77 MissingBrain,
78 #[error("brain artifact is mounted as participant '{actual}', expected 'brain'")]
79 BrainIdMismatch { actual: ParticipantId },
80 #[error("runtime graph contains more than one brain participant")]
81 DuplicateBrain,
82 #[error("simulated runtime graph has no simulator world authority")]
83 MissingSimulator,
84 #[error("simulated runtime graph contains more than one simulator world authority")]
85 DuplicateSimulator,
86 #[error(
87 "participant '{participant}' clock {participant_clock:?} conflicts with robot clock {robot:?}"
88 )]
89 ClockMismatch {
90 participant: ParticipantId,
91 robot: Clock,
92 participant_clock: ParticipantClock,
93 },
94 #[error(
95 "{kind:?} participant '{participant}' clock {participant_clock:?} is incompatible with robot clock {robot:?}"
96 )]
97 ExecutionModeMismatch {
98 participant: ParticipantId,
99 kind: ParticipantKind,
100 robot: Clock,
101 participant_clock: ParticipantClock,
102 },
103 #[error(
104 "artifact '{artifact}' requires {requirement:?}, but the robot uses {actual} kinematics"
105 )]
106 RequirementKinematicsMismatch {
107 artifact: ParticipantArtifactId,
108 requirement: ParticipantRequirement,
109 actual: phoxal_model::robot::KinematicKind,
110 },
111 #[error("artifact '{artifact}' requires at least one {side} actuator")]
112 RequirementActuatorListEmpty {
113 artifact: ParticipantArtifactId,
114 side: &'static str,
115 },
116 #[error("artifact '{artifact}' requirement could not resolve actuator '{actuator}': {error}")]
117 RequirementActuatorInvalid {
118 artifact: ParticipantArtifactId,
119 actuator: CapabilityRef,
120 error: String,
121 },
122 #[error(
123 "artifact '{artifact}' actuator '{actuator}' is configured for {actual:?}, but the binary requires {expected:?}"
124 )]
125 RequirementMotorModeMismatch {
126 artifact: ParticipantArtifactId,
127 actuator: CapabilityRef,
128 expected: MotorCommand,
129 actual: MotorCommand,
130 },
131 #[error("artifact '{artifact}' has an invalid config schema: {error}")]
132 InvalidConfigSchema {
133 artifact: ParticipantArtifactId,
134 error: String,
135 },
136 #[error("participant '{participant}' config does not match its binary schema: {error}")]
137 InvalidConfig {
138 participant: ParticipantId,
139 error: String,
140 },
141 #[error("asset id '{id}' is persisted at the wrong path '{path}'", id = id.as_str())]
142 AssetPathMismatch { id: AssetId, path: BundlePath },
143 #[error("asset path is outside assets/: {path}")]
144 AssetOutsideAssets { path: BundlePath },
145 #[error("duplicate asset id '{id}'", id = id.as_str())]
146 DuplicateAssetId { id: AssetId },
147 #[error("duplicate asset path '{path}'")]
148 DuplicateAssetPath { path: BundlePath },
149 #[error("router config is outside assets/: {path}")]
150 RouterOutsideAssets { path: BundlePath },
151 #[error("router config is not present in the asset index: {path}")]
152 RouterMissingAsset { path: BundlePath },
153 #[error("supplied asset bytes do not match the persisted asset index")]
154 AssetIndexMismatch,
155 #[error("supplied executable sources do not match the persisted artifact set")]
156 BinaryIndexMismatch,
157 #[error("bundle path is not valid: {0}")]
158 Path(#[from] BundlePathError),
159}
160
161#[derive(Clone, Debug, thiserror::Error)]
163pub enum SelectionError {
164 #[error("runtime bundle has no participant '{requested}'")]
165 Unknown { requested: ParticipantId },
166 #[error("participant '{participant}' references missing artifact '{artifact}'")]
167 MissingArtifact {
168 participant: ParticipantId,
169 artifact: ParticipantArtifactId,
170 },
171}
172
173#[derive(Debug, thiserror::Error)]
175pub enum BundleError {
176 #[error("failed to resolve bundle root {}: {source}", path.display())]
177 Root {
178 path: PathBuf,
179 #[source]
180 source: std::io::Error,
181 },
182 #[error("bundle root is not a directory: {0}")]
183 NotDirectory(PathBuf),
184 #[error("bundle target already exists: {0}")]
185 TargetExists(PathBuf),
186 #[error("failed to read runtime document {}: {source}", path.display())]
187 ReadDocument {
188 path: PathBuf,
189 #[source]
190 source: std::io::Error,
191 },
192 #[error("runtime document is not valid JSON: {0}")]
193 DocumentJson(#[from] serde_json::Error),
194 #[error(transparent)]
195 Document(#[from] DocumentError),
196 #[error("bundle contains unsupported filesystem entry {path}")]
197 UnsupportedEntry { path: PathBuf },
198 #[error("bundle executable is not executable: {path}")]
199 NotExecutable { path: PathBuf },
200 #[error("bundle contains unexpected file {path}")]
201 UnexpectedFile { path: PathBuf },
202 #[error("bundle contains unindexed directory {path}")]
203 UnindexedDirectory { path: PathBuf },
204 #[error("bundle is missing required file {path}")]
205 MissingFile { path: PathBuf },
206 #[error("failed to read bundle file {path}: {source}", path = path.display())]
207 ReadFile {
208 path: PathBuf,
209 #[source]
210 source: std::io::Error,
211 },
212 #[error("bundle file {path} changed after indexing: expected {expected}, found {actual}")]
213 Integrity {
214 path: PathBuf,
215 expected: Sha256Digest,
216 actual: Sha256Digest,
217 },
218 #[error("bundle file {path} has size {actual}, expected {expected}")]
219 Size {
220 path: PathBuf,
221 expected: u64,
222 actual: u64,
223 },
224 #[error("asset '{id}' is not declared by runtime.json", id = id.as_str())]
225 UndeclaredAsset { id: AssetId },
226 #[error(transparent)]
227 Path(#[from] BundlePathError),
228 #[error(transparent)]
229 Digest(#[from] DigestError),
230 #[error(transparent)]
231 Selection(#[from] SelectionError),
232}