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("supplied asset bytes do not match the persisted asset index")]
150 AssetIndexMismatch,
151 #[error("supplied executable sources do not match the persisted artifact set")]
152 BinaryIndexMismatch,
153 #[error("bundle path is not valid: {0}")]
154 Path(#[from] BundlePathError),
155}
156
157#[derive(Clone, Debug, thiserror::Error)]
159pub enum SelectionError {
160 #[error("runtime bundle has no participant '{requested}'")]
161 Unknown { requested: ParticipantId },
162 #[error("participant '{participant}' references missing artifact '{artifact}'")]
163 MissingArtifact {
164 participant: ParticipantId,
165 artifact: ParticipantArtifactId,
166 },
167}
168
169#[derive(Debug, thiserror::Error)]
171pub enum BundleError {
172 #[error("failed to resolve bundle root {}: {source}", path.display())]
173 Root {
174 path: PathBuf,
175 #[source]
176 source: std::io::Error,
177 },
178 #[error("bundle root is not a directory: {0}")]
179 NotDirectory(PathBuf),
180 #[error("bundle target already exists: {0}")]
181 TargetExists(PathBuf),
182 #[error("failed to read runtime document {}: {source}", path.display())]
183 ReadDocument {
184 path: PathBuf,
185 #[source]
186 source: std::io::Error,
187 },
188 #[error("runtime document is not valid JSON: {0}")]
189 DocumentJson(#[from] serde_json::Error),
190 #[error(transparent)]
191 Document(#[from] DocumentError),
192 #[error("bundle contains unsupported filesystem entry {path}")]
193 UnsupportedEntry { path: PathBuf },
194 #[error("bundle executable is not executable: {path}")]
195 NotExecutable { path: PathBuf },
196 #[error("bundle contains unexpected file {path}")]
197 UnexpectedFile { path: PathBuf },
198 #[error("bundle contains unindexed directory {path}")]
199 UnindexedDirectory { path: PathBuf },
200 #[error("bundle is missing required file {path}")]
201 MissingFile { path: PathBuf },
202 #[error("failed to read bundle file {path}: {source}", path = path.display())]
203 ReadFile {
204 path: PathBuf,
205 #[source]
206 source: std::io::Error,
207 },
208 #[error("bundle file {path} changed after indexing: expected {expected}, found {actual}")]
209 Integrity {
210 path: PathBuf,
211 expected: Sha256Digest,
212 actual: Sha256Digest,
213 },
214 #[error("bundle file {path} has size {actual}, expected {expected}")]
215 Size {
216 path: PathBuf,
217 expected: u64,
218 actual: u64,
219 },
220 #[error("asset '{id}' is not declared by runtime.json", id = id.as_str())]
221 UndeclaredAsset { id: AssetId },
222 #[error(transparent)]
223 Path(#[from] BundlePathError),
224 #[error(transparent)]
225 Digest(#[from] DigestError),
226 #[error(transparent)]
227 Selection(#[from] SelectionError),
228}