1use a3s_acl::{CanonicalError, ParseError, SchemaDiagnosticCode};
2use std::fmt;
3
4#[non_exhaustive]
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum AgentReleaseField {
8 Contract,
9 Protocol,
10 ArtifactDigest,
11 ArtifactMediaType,
12 EntrypointCommand,
13 EntrypointArgument,
14 HealthTransport,
15 HealthPort,
16 ReadinessPath,
17 LivenessPath,
18 ShutdownGraceSeconds,
19 WorkspaceMode,
20 CacheMode,
21 PersistentDataMode,
22 CapabilityName,
23 CapabilityLevel,
24 SecretName,
25 SecretTarget,
26 SecretDestination,
27 ProvenanceKind,
28 ProvenanceUri,
29 ProvenanceDigest,
30}
31
32impl fmt::Display for AgentReleaseField {
33 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
34 formatter.write_str(match self {
35 Self::Contract => "agent_release.schema",
36 Self::Protocol => "agent_release.protocol",
37 Self::ArtifactDigest => "agent_release.artifact.digest",
38 Self::ArtifactMediaType => "agent_release.artifact.media_type",
39 Self::EntrypointCommand => "agent_release.entrypoint.command",
40 Self::EntrypointArgument => "agent_release.entrypoint.args",
41 Self::HealthTransport => "agent_release.health.transport",
42 Self::HealthPort => "agent_release.health.port",
43 Self::ReadinessPath => "agent_release.health.readiness_path",
44 Self::LivenessPath => "agent_release.health.liveness_path",
45 Self::ShutdownGraceSeconds => "agent_release.health.shutdown_grace_seconds",
46 Self::WorkspaceMode => "agent_release.storage.workspace",
47 Self::CacheMode => "agent_release.storage.cache",
48 Self::PersistentDataMode => "agent_release.storage.persistent_data",
49 Self::CapabilityName => "agent_release.capability.name",
50 Self::CapabilityLevel => "agent_release.capability.level",
51 Self::SecretName => "agent_release.secret.name",
52 Self::SecretTarget => "agent_release.secret.target",
53 Self::SecretDestination => "agent_release.secret.destination",
54 Self::ProvenanceKind => "agent_release.provenance.kind",
55 Self::ProvenanceUri => "agent_release.provenance.uri",
56 Self::ProvenanceDigest => "agent_release.provenance.digest",
57 })
58 }
59}
60
61#[non_exhaustive]
63#[derive(Debug)]
64pub enum AgentReleaseError {
65 Io(std::io::Error),
66 InputTooLarge,
67 InvalidEncoding,
68 Parse(ParseError),
69 Schema {
70 diagnostic: SchemaDiagnosticCode,
71 truncated: bool,
72 },
73 SchemaBudgetExceeded,
74 Canonical(CanonicalError),
75 CanonicalEncoding,
76 UnsupportedContract,
77 InvalidField(AgentReleaseField),
78 DuplicateCapability,
79 DuplicateSecret,
80 DuplicateProvenance,
81 IncompatibleProtocol,
82 UnsupportedCapability {
83 required_index: usize,
84 },
85}
86
87impl AgentReleaseError {
88 pub const fn code(&self) -> &'static str {
90 match self {
91 Self::Io(_) => "a3s.code.agent_release.io",
92 Self::InputTooLarge | Self::InvalidEncoding | Self::Parse(_) => {
93 "a3s.code.agent_release.parse"
94 }
95 Self::Schema { .. } | Self::SchemaBudgetExceeded => "a3s.code.agent_release.schema",
96 Self::Canonical(_) | Self::CanonicalEncoding => "a3s.code.agent_release.canonical",
97 Self::UnsupportedContract => "a3s.code.agent_release.unsupported_contract",
98 Self::InvalidField(_) => "a3s.code.agent_release.invalid_field",
99 Self::DuplicateCapability => "a3s.code.agent_release.duplicate_capability",
100 Self::DuplicateSecret => "a3s.code.agent_release.duplicate_secret",
101 Self::DuplicateProvenance => "a3s.code.agent_release.duplicate_provenance",
102 Self::IncompatibleProtocol => "a3s.code.agent_release.incompatible_protocol",
103 Self::UnsupportedCapability { .. } => "a3s.code.agent_release.unsupported_capability",
104 }
105 }
106
107 pub const fn field(&self) -> Option<AgentReleaseField> {
109 match self {
110 Self::InvalidField(field) => Some(*field),
111 _ => None,
112 }
113 }
114}
115
116impl fmt::Display for AgentReleaseError {
117 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
118 match self {
119 Self::Io(error) => write!(formatter, "could not read Agent release manifest: {error}"),
120 Self::InputTooLarge => {
121 formatter.write_str("Agent release manifest exceeds its input budget")
122 }
123 Self::InvalidEncoding => {
124 formatter.write_str("Agent release manifest must be valid UTF-8")
125 }
126 Self::Parse(error) => write!(formatter, "Agent release manifest parse failed: {error}"),
127 Self::Schema {
128 diagnostic,
129 truncated,
130 } => {
131 write!(
132 formatter,
133 "Agent release manifest schema rejected the document ({diagnostic})"
134 )?;
135 if *truncated {
136 formatter.write_str("; additional diagnostics were truncated")?;
137 }
138 Ok(())
139 }
140 Self::SchemaBudgetExceeded => {
141 formatter.write_str("Agent release schema diagnostics exceeded their budget")
142 }
143 Self::Canonical(error) => {
144 write!(formatter, "Agent release canonicalization failed: {error}")
145 }
146 Self::CanonicalEncoding => {
147 formatter.write_str("Agent release canonical bytes are not UTF-8")
148 }
149 Self::UnsupportedContract => {
150 formatter.write_str("Agent release contract version is not supported")
151 }
152 Self::InvalidField(field) => {
153 write!(formatter, "Agent release field {field} is invalid")
154 }
155 Self::DuplicateCapability => {
156 formatter.write_str("Agent release capability requirements must be unique")
157 }
158 Self::DuplicateSecret => {
159 formatter.write_str("Agent release secret requirements must be unique")
160 }
161 Self::DuplicateProvenance => {
162 formatter.write_str("Agent release provenance kinds must be unique")
163 }
164 Self::IncompatibleProtocol => {
165 formatter.write_str("Agent release protocol is not supported by the runtime")
166 }
167 Self::UnsupportedCapability { required_index } => write!(
168 formatter,
169 "Agent release capability requirement at index {required_index} is unavailable"
170 ),
171 }
172 }
173}
174
175impl std::error::Error for AgentReleaseError {
176 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
177 match self {
178 Self::Io(error) => Some(error),
179 Self::Parse(error) => Some(error),
180 Self::Canonical(error) => Some(error),
181 _ => None,
182 }
183 }
184}
185
186impl From<std::io::Error> for AgentReleaseError {
187 fn from(error: std::io::Error) -> Self {
188 Self::Io(error)
189 }
190}
191
192impl From<ParseError> for AgentReleaseError {
193 fn from(error: ParseError) -> Self {
194 Self::Parse(error)
195 }
196}
197
198impl From<CanonicalError> for AgentReleaseError {
199 fn from(error: CanonicalError) -> Self {
200 Self::Canonical(error)
201 }
202}