wip_protocol/error.rs
1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3
4/// Namespace in which a declaration name must be unique.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum NameNamespace {
7 /// Named types in an interface descriptor.
8 TypeDeclaration,
9 /// Operations in an interface descriptor.
10 Operation,
11 /// Fields in an inline or named record type.
12 Field,
13 /// Parameters in an operation declaration.
14 Parameter,
15 /// Cases in an enum type.
16 EnumCase,
17 /// Cases in a union type.
18 UnionCase,
19 /// Child object names beneath one tree node.
20 ChildObject,
21}
22
23/// Structural kind of a schema-neutral logical value.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum ValueKind {
26 /// Unit value.
27 Unit,
28 /// Boolean value.
29 Boolean,
30 /// Integer value.
31 Integer,
32 /// Floating-point number value.
33 Number,
34 /// String value.
35 String,
36 /// Byte sequence.
37 Bytes,
38 /// Record value.
39 Record,
40 /// List value.
41 List,
42}
43
44/// A typed segment identifying a declaration or value location.
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum PathSegment {
47 /// Descriptor root.
48 Descriptor,
49 /// Named type declaration.
50 Declaration(String),
51 /// Operation declaration or call.
52 Operation(String),
53 /// Record field declaration or value.
54 Field(String),
55 /// Operation parameter or argument.
56 Parameter(String),
57 /// Enum or union case.
58 Case(String),
59 /// List index.
60 Index(usize),
61 /// Operation return value.
62 Return,
63 /// Object at a Worldspace path.
64 Object(String),
65 /// Child object in an observation response.
66 Child(String),
67}
68
69/// Machine-readable reason why logical protocol validation failed.
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum ValidationErrorKind {
72 /// A name occurred more than once in one namespace.
73 DuplicateName {
74 /// Namespace containing the duplicate.
75 namespace: NameNamespace,
76 /// Duplicate name.
77 name: String,
78 },
79 /// A named type was not declared in the descriptor.
80 UnknownType {
81 /// Missing descriptor-local type name.
82 name: String,
83 },
84 /// Named declarations form a recursive cycle.
85 RecursiveType {
86 /// Cycle in traversal order, with the starting name repeated last.
87 cycle: Vec<String>,
88 },
89 /// A value has the wrong structural kind.
90 TypeMismatch {
91 /// Expected kind.
92 expected: ValueKind,
93 /// Actual kind.
94 actual: ValueKind,
95 },
96 /// A record value contains an undeclared field.
97 UnknownField {
98 /// Unknown field name.
99 name: String,
100 },
101 /// A record value omitted a required field.
102 MissingField {
103 /// Missing field name.
104 name: String,
105 },
106 /// An enum or union selected an undeclared case.
107 UnknownCase {
108 /// Unknown case name.
109 name: String,
110 },
111 /// A payload-bearing union case omitted `value`.
112 MissingUnionPayload,
113 /// A payload-free union case supplied `value`.
114 UnexpectedUnionPayload,
115 /// An operation call contains an undeclared argument.
116 UnknownParameter {
117 /// Unknown parameter name.
118 name: String,
119 },
120 /// An operation call omitted a required argument.
121 MissingParameter {
122 /// Missing parameter name.
123 name: String,
124 },
125 /// An operation is not declared by the descriptor.
126 UnknownOperation {
127 /// Unknown operation name.
128 name: String,
129 },
130 /// An integer is outside the interoperable safe range.
131 IntegerOutOfRange {
132 /// Rejected integer.
133 value: i64,
134 },
135 /// A numeric value is NaN or infinite.
136 NonFiniteNumber,
137 /// A JSON-interpreted value tree contains protocol bytes.
138 BytesInJson,
139 /// An object name is not a valid root or non-root path segment.
140 InvalidObjectName {
141 /// Rejected object name.
142 name: String,
143 },
144 /// An object's ordered interface set contains a duplicate.
145 DuplicateInterface {
146 /// Duplicate opaque interface reference.
147 reference: String,
148 },
149 /// A string is not a canonical absolute Worldspace path.
150 InvalidPath {
151 /// Rejected path.
152 path: String,
153 },
154 /// An object name does not match the path where it was observed.
155 ObjectNameMismatch {
156 /// Name required by the observation path.
157 expected: String,
158 /// Name supplied by the object.
159 actual: String,
160 },
161 /// Equivalent ref/validator observations disagree on object representation.
162 InconsistentObjectObservation {
163 /// Shared opaque object reference.
164 reference: String,
165 },
166 /// An observation omitted children before reaching the requested depth.
167 ChildrenMissingBeforeDepth {
168 /// Node depth where children were required.
169 depth: u32,
170 },
171 /// An observation included children at the requested depth boundary.
172 ChildrenAtDepthBoundary {
173 /// Boundary depth where children must be unobserved.
174 depth: u32,
175 },
176 /// A response refers to a different interface than its request.
177 InterfaceResponseMismatch {
178 /// Requested opaque reference.
179 expected: String,
180 /// Returned opaque reference.
181 actual: String,
182 },
183}
184
185/// A validation failure with a typed path to the failing location.
186#[derive(Debug, Clone, PartialEq, Eq)]
187pub struct ValidationError {
188 /// Path from the validation root to the failure.
189 pub path: Vec<PathSegment>,
190 /// Machine-readable failure reason.
191 pub kind: ValidationErrorKind,
192}
193
194impl ValidationError {
195 pub(crate) fn new(kind: ValidationErrorKind) -> Self {
196 Self {
197 path: Vec::new(),
198 kind,
199 }
200 }
201
202 pub(crate) fn prepend(mut self, segment: PathSegment) -> Self {
203 self.path.insert(0, segment);
204 self
205 }
206}
207
208impl Display for ValidationError {
209 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
210 if self.path.is_empty() {
211 write!(formatter, "protocol validation failed: {:?}", self.kind)
212 } else {
213 write!(
214 formatter,
215 "protocol validation failed at {:?}: {:?}",
216 self.path, self.kind
217 )
218 }
219 }
220}
221
222impl Error for ValidationError {}
223
224/// One core interaction whose failure codes form a closed set.
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum ProtocolInteraction {
227 /// Observe one object and its indexable descendants to a requested depth.
228 Observe,
229 /// Fetch one interface descriptor by its opaque reference.
230 FetchInterface,
231 /// Invoke one operation on a path-targeted object.
232 CallOperation,
233}
234
235/// Stable protocol failure code returned by a host.
236///
237/// This is the complete wire-level code set. A Client treats a malformed success
238/// or failure response, including a code disallowed for its interaction, as a
239/// Client-local `InvalidResponse`. An unsupported descriptor format is likewise
240/// a Client-local `UnsupportedDescriptorFormat`; neither condition is a code a
241/// Host can return.
242#[derive(Debug, Clone, Copy, PartialEq, Eq)]
243pub enum ProtocolErrorCode {
244 /// The request is structurally invalid before an operation descriptor is fixed.
245 InvalidRequest,
246 /// The target object is not published.
247 NotFound,
248 /// The interface reference is not currently published.
249 InterfaceNotFound,
250 /// The selected interface is not on the current object.
251 InterfaceMismatch,
252 /// The selected operation is not declared by the current interface.
253 OperationNotFound,
254 /// Supplied arguments do not match the fixed operation declaration.
255 InvalidArguments,
256 /// The operation requires an object validator and none was supplied.
257 ValidatorRequired,
258 /// The supplied object validator does not match current state.
259 ValidatorMismatch,
260 /// A mutable interface requires its validator and none was supplied.
261 InterfaceValidatorRequired,
262 /// The supplied interface validator does not match the descriptor.
263 InterfaceValidatorMismatch,
264 /// A published operation is not permitted for the caller.
265 PermissionDenied,
266 /// A bounded protocol resource limit was exceeded.
267 ///
268 /// For `call_operation`, the Host guarantees that the operation was not run
269 /// or that all of its effects were rolled back.
270 ResourceLimitExceeded,
271 /// The Host encountered an internal failure.
272 ///
273 /// For `call_operation`, the Host guarantees that the operation was not run
274 /// or that all of its effects were rolled back.
275 Internal,
276 /// The Host cannot guarantee that the operation was not run or rolled back.
277 ///
278 /// A Client must not automatically retry because the operation may have
279 /// committed effects even though no success response was available.
280 OperationOutcomeUnknown,
281}
282
283impl ProtocolErrorCode {
284 /// Every canonical wire-level error code.
285 pub const ALL: [Self; 14] = [
286 Self::InvalidRequest,
287 Self::NotFound,
288 Self::InterfaceNotFound,
289 Self::InterfaceMismatch,
290 Self::OperationNotFound,
291 Self::InvalidArguments,
292 Self::ValidatorRequired,
293 Self::ValidatorMismatch,
294 Self::InterfaceValidatorRequired,
295 Self::InterfaceValidatorMismatch,
296 Self::PermissionDenied,
297 Self::ResourceLimitExceeded,
298 Self::Internal,
299 Self::OperationOutcomeUnknown,
300 ];
301
302 /// Whether a Host may return this code for `interaction`.
303 ///
304 /// `false` identifies an invalid error response; it does not turn that
305 /// response into another wire-level protocol failure.
306 #[must_use]
307 pub const fn is_allowed_for(self, interaction: ProtocolInteraction) -> bool {
308 use ProtocolErrorCode::{
309 InterfaceMismatch, InterfaceNotFound, InterfaceValidatorMismatch,
310 InterfaceValidatorRequired, Internal, InvalidArguments, InvalidRequest, NotFound,
311 OperationNotFound, OperationOutcomeUnknown, PermissionDenied, ResourceLimitExceeded,
312 ValidatorMismatch, ValidatorRequired,
313 };
314 use ProtocolInteraction::{CallOperation, FetchInterface, Observe};
315
316 match (interaction, self) {
317 (_, InvalidRequest | ResourceLimitExceeded | Internal) => true,
318 (Observe, NotFound) => true,
319 (FetchInterface, InterfaceNotFound) => true,
320 (
321 CallOperation,
322 NotFound
323 | InterfaceMismatch
324 | OperationNotFound
325 | InvalidArguments
326 | ValidatorRequired
327 | ValidatorMismatch
328 | InterfaceValidatorRequired
329 | InterfaceValidatorMismatch
330 | PermissionDenied
331 | OperationOutcomeUnknown,
332 ) => true,
333 (
334 _,
335 NotFound
336 | InterfaceNotFound
337 | InterfaceMismatch
338 | OperationNotFound
339 | InvalidArguments
340 | ValidatorRequired
341 | ValidatorMismatch
342 | InterfaceValidatorRequired
343 | InterfaceValidatorMismatch
344 | PermissionDenied
345 | OperationOutcomeUnknown,
346 ) => false,
347 }
348 }
349}
350
351/// Transport-independent protocol failure.
352#[derive(Debug, Clone, PartialEq, Eq)]
353pub struct ProtocolError {
354 /// Stable programmatic code. Consumers must branch on this field.
355 pub code: ProtocolErrorCode,
356 /// Bounded human-readable diagnostic. Consumers must not branch on it.
357 pub message: String,
358}
359
360impl Display for ProtocolError {
361 fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
362 write!(
363 formatter,
364 "protocol failure ({:?}): {}",
365 self.code, self.message
366 )
367 }
368}
369
370impl Error for ProtocolError {}