1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use thiserror::Error;
use crate::{
protocol::{JobState, ProtocolVersion},
provider::ProviderFeature,
};
/// Result returned by remote-build protocol and provider operations.
pub type RemoteBuildResult<T> = Result<T, RemoteBuildError>;
/// Typed failures crossing the remote-build boundary.
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum RemoteBuildError {
/// The peer uses an incompatible protocol major version.
#[error("remote protocol version {received} is incompatible; this client supports {supported}")]
IncompatibleProtocolVersion {
/// Highest protocol version understood by this implementation.
supported: ProtocolVersion,
/// Version sent by the peer.
received: ProtocolVersion,
},
/// A stream ended before one complete JSON event was available.
#[error("remote event ended before a complete JSON object was received")]
TruncatedEvent,
/// A remote event was not valid JSON or did not match the protocol envelope.
#[error("remote event is malformed: {message}")]
MalformedEvent {
/// Bounded parser description; never raw worker output.
message: String,
},
/// Event bytes were not UTF-8.
#[error("remote event is not valid UTF-8")]
InvalidUtf8,
/// One event exceeded the protocol decoder limit.
#[error("remote event is {bytes} bytes; maximum accepted size is {maximum} bytes")]
EventTooLarge {
/// Received byte count.
bytes: usize,
/// Decoder limit.
maximum: usize,
},
/// A stable identifier was empty, too long, or contained control characters.
#[error("remote field `{field}` is invalid: {reason}")]
InvalidIdentifier {
/// Protocol field name.
field: &'static str,
/// Stable validation reason.
reason: &'static str,
},
/// Event text contained terminal control sequences.
#[error("remote field `{field}` contains terminal control data")]
UnsafeEventText {
/// Protocol field containing unsafe text.
field: &'static str,
},
/// A typed event carried inconsistent or impossible values.
#[error("remote event `{event}` is invalid: {reason}")]
InvalidEventPayload {
/// Stable event name.
event: &'static str,
/// Stable validation reason.
reason: &'static str,
},
/// The required source manifest was malformed or non-canonical.
#[error("source manifest is invalid: {message}")]
InvalidSourceManifest {
/// Sanitized source-validation summary.
message: String,
},
/// A provider reported an impossible job-state change.
#[error("job cannot transition from {from:?} to {to:?}")]
InvalidJobTransition {
/// State before the transition.
from: JobState,
/// Requested state.
to: JobState,
},
/// The caller requested cancellation.
#[error("remote operation was cancelled")]
Cancelled,
/// The selected provider cannot satisfy a required feature.
#[error("provider `{provider}` does not support {feature}")]
UnsupportedCapability {
/// Stable provider identifier.
provider: String,
/// Missing typed feature.
feature: ProviderFeature,
},
/// A provider failed with a stable provider-specific code.
#[error("provider `{provider}` failed with `{code}`: {message}")]
ProviderFailure {
/// Stable provider identifier.
provider: String,
/// Stable provider-specific error code.
code: String,
/// Sanitized failure summary.
message: String,
/// Whether retrying the same operation may succeed.
retryable: bool,
},
/// A requested artifact is absent from the job manifest.
#[error("artifact `{artifact_id}` was not found for job `{job_id}`")]
ArtifactNotFound {
/// Stable job identifier.
job_id: String,
/// Stable artifact identifier.
artifact_id: String,
},
/// Downloaded bytes do not match the signed manifest digest.
#[error("artifact `{artifact_id}` digest mismatch: expected {expected}, received {actual}")]
IntegrityMismatch {
/// Stable artifact identifier.
artifact_id: String,
/// Manifest SHA-256 digest.
expected: String,
/// Downloaded SHA-256 digest.
actual: String,
},
/// A local protocol object could not be serialized.
#[error("could not serialize remote protocol data: {message}")]
Serialization {
/// Bounded serializer description.
message: String,
},
}
impl RemoteBuildError {
/// Stable machine-readable error code.
pub const fn code(&self) -> &'static str {
match self {
Self::IncompatibleProtocolVersion { .. } => "incompatible_protocol_version",
Self::TruncatedEvent => "truncated_event",
Self::MalformedEvent { .. } => "malformed_event",
Self::InvalidUtf8 => "invalid_utf8",
Self::EventTooLarge { .. } => "event_too_large",
Self::InvalidIdentifier { .. } => "invalid_identifier",
Self::UnsafeEventText { .. } => "unsafe_event_text",
Self::InvalidEventPayload { .. } => "invalid_event_payload",
Self::InvalidSourceManifest { .. } => "invalid_source_manifest",
Self::InvalidJobTransition { .. } => "invalid_job_transition",
Self::Cancelled => "cancelled",
Self::UnsupportedCapability { .. } => "unsupported_capability",
Self::ProviderFailure { .. } => "provider_failure",
Self::ArtifactNotFound { .. } => "artifact_not_found",
Self::IntegrityMismatch { .. } => "integrity_mismatch",
Self::Serialization { .. } => "serialization_failed",
}
}
/// Whether retrying the same operation may succeed without user changes.
pub const fn retryable(&self) -> bool {
match self {
Self::ProviderFailure { retryable, .. } => *retryable,
Self::TruncatedEvent | Self::Cancelled => true,
Self::IncompatibleProtocolVersion { .. }
| Self::MalformedEvent { .. }
| Self::InvalidUtf8
| Self::EventTooLarge { .. }
| Self::InvalidIdentifier { .. }
| Self::UnsafeEventText { .. }
| Self::InvalidEventPayload { .. }
| Self::InvalidSourceManifest { .. }
| Self::InvalidJobTransition { .. }
| Self::UnsupportedCapability { .. }
| Self::ArtifactNotFound { .. }
| Self::IntegrityMismatch { .. }
| Self::Serialization { .. } => false,
}
}
}