Skip to main content

phoxal_bundle/
error.rs

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