Skip to main content

atm_storage/
error_codes.rs

1//! Stable ATM-owned error-code registry.
2
3use std::fmt;
4use std::str::FromStr;
5
6use serde::{Deserialize, Deserializer, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct UnknownAtmErrorCode(pub String);
10
11impl fmt::Display for UnknownAtmErrorCode {
12    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13        write!(f, "unknown ATM error code: {}", self.0)
14    }
15}
16
17impl std::error::Error for UnknownAtmErrorCode {}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub enum AtmErrorCode {
21    ConfigHomeUnavailable,
22    AtmHomeUnresolved,
23    ConfigParseFailed,
24    ConfigRetiredHookMembersKey,
25    ConfigRetiredLegacyHookKeys,
26    ConfigTeamParseFailed,
27    ConfigTeamMissing,
28    IdentityUnavailable,
29    IdentityInvalid,
30    IdentityConflict,
31    MemberAlreadyExists,
32    MemberNotFound,
33    DaemonUnavailable,
34    RuntimeRootInvalid,
35    RuntimeBootstrapRefused,
36    SocketOverrideForbidden,
37    DaemonMayHaveExecuted,
38    DaemonLifecycleWedge,
39    DaemonLaunchGateRejected,
40    DaemonServingStateRejected,
41    DaemonStaleOwnerRecoveryFailed,
42    DaemonAutoStartFailed,
43    DaemonConnectionSaturated,
44    ClientDaemonVersionIncompatible,
45    DaemonAdvisorySessionAlreadyRegistered,
46    DaemonAdvisorySessionNotRegistered,
47    DaemonAdvisorySessionCleanupFailed,
48    RemoteDeliveryOutcomeUnknown,
49    AddressParseFailed,
50    TeamUnavailable,
51    TeamInvalid,
52    TeamNotFound,
53    AgentNotFound,
54    MailboxReadFailed,
55    MailboxWriteFailed,
56    MailboxLockFailed,
57    MailboxLockReadOnlyFilesystem,
58    MailboxLockTimeout,
59    InternalError,
60    MessageValidationFailed,
61    SelfAddressedSendInvalid,
62    EmptyNudgeTemplateBody,
63    CallerContextRequestInvalid,
64    SerializationFailed,
65    FilePolicyRejected,
66    FileReferenceRewriteFailed,
67    WaitTimeout,
68    AckInvalidState,
69    ClearInvalidState,
70    ObservabilityEmitFailed,
71    ObservabilityQueryFailed,
72    ObservabilityFollowFailed,
73    ObservabilityHealthFailed,
74    ObservabilityBootstrapFailed,
75    ObservabilityHealthOk,
76    WarningInvalidTeamMemberSkipped,
77    WarningMailboxRecordSkipped,
78    WarningMalformedAtmFieldIgnored,
79    WarningObservabilityHealthDegraded,
80    WarningSqliteHealthDegraded,
81    WarningOriginInboxEntrySkipped,
82    WarningMissingTeamConfigFallback,
83    WarningSendAlertStateDegraded,
84    WarningIdentityDrift,
85    WarningRosterDrift,
86    WarningBaselineMemberMissing,
87    WarningRestoreInProgress,
88    WarningStaleMailboxLock,
89    WarningHookSkipped,
90    WarningHookExecutionFailed,
91    PostSendPaneMissing,
92    PostSendTmuxSendFailed,
93    PostSendGraftUnavailable,
94    PostSendAdvisoryDeliveryFailed,
95    TestFakeTransportInjectionFailed,
96    HelpTopicNotFound,
97}
98
99impl AtmErrorCode {
100    pub fn as_str(self) -> &'static str {
101        if let Some(value) = self.config_or_identity_str() {
102            return value;
103        }
104        if let Some(value) = self.daemon_or_address_str() {
105            return value;
106        }
107        if let Some(value) = self.mailbox_or_validation_str() {
108            return value;
109        }
110        if let Some(value) = self.observability_or_warning_str() {
111            return value;
112        }
113        self.post_send_or_misc_str()
114    }
115
116    fn config_or_identity_str(self) -> Option<&'static str> {
117        Some(match self {
118            Self::ConfigHomeUnavailable => "ATM_CONFIG_HOME_UNAVAILABLE",
119            Self::AtmHomeUnresolved => "ATM_HOME_UNRESOLVED",
120            Self::ConfigParseFailed => "ATM_CONFIG_PARSE_FAILED",
121            Self::ConfigRetiredHookMembersKey => "ATM_CONFIG_RETIRED_HOOK_MEMBERS_KEY",
122            Self::ConfigRetiredLegacyHookKeys => "ATM_CONFIG_RETIRED_LEGACY_HOOK_KEYS",
123            Self::ConfigTeamParseFailed => "ATM_CONFIG_TEAM_PARSE_FAILED",
124            Self::ConfigTeamMissing => "ATM_CONFIG_TEAM_MISSING",
125            Self::IdentityUnavailable => "ATM_IDENTITY_UNAVAILABLE",
126            Self::IdentityInvalid => "ATM_IDENTITY_INVALID",
127            Self::IdentityConflict => "ATM_IDENTITY_CONFLICT",
128            Self::MemberAlreadyExists => "ATM_MEMBER_ALREADY_EXISTS",
129            Self::MemberNotFound => "ATM_MEMBER_NOT_FOUND",
130            _ => return None,
131        })
132    }
133
134    fn daemon_or_address_str(self) -> Option<&'static str> {
135        Some(match self {
136            Self::DaemonUnavailable => "ATM_DAEMON_UNAVAILABLE",
137            Self::RuntimeRootInvalid => "ATM_RUNTIME_ROOT_INVALID",
138            Self::RuntimeBootstrapRefused => "ATM_RUNTIME_BOOTSTRAP_REFUSED",
139            Self::SocketOverrideForbidden => "ATM_SOCKET_OVERRIDE_FORBIDDEN",
140            Self::DaemonMayHaveExecuted => "ATM_DAEMON_MAY_HAVE_EXECUTED",
141            Self::DaemonLifecycleWedge => "ATM_DAEMON_LIFECYCLE_WEDGE",
142            Self::DaemonLaunchGateRejected => "ATM_DAEMON_LAUNCH_GATE_REJECTED",
143            Self::DaemonServingStateRejected => "ATM_DAEMON_SERVING_STATE_REJECTED",
144            Self::DaemonStaleOwnerRecoveryFailed => "ATM_DAEMON_STALE_OWNER_RECOVERY_FAILED",
145            Self::DaemonAutoStartFailed => "ATM_DAEMON_AUTO_START_FAILED",
146            Self::DaemonConnectionSaturated => "ATM_DAEMON_CONNECTION_SATURATED",
147            Self::ClientDaemonVersionIncompatible => "ATM_CLIENT_DAEMON_VERSION_INCOMPATIBLE",
148            Self::DaemonAdvisorySessionAlreadyRegistered => {
149                "ATM_DAEMON_ADVISORY_SESSION_ALREADY_REGISTERED"
150            }
151            Self::DaemonAdvisorySessionNotRegistered => {
152                "ATM_DAEMON_ADVISORY_SESSION_NOT_REGISTERED"
153            }
154            Self::DaemonAdvisorySessionCleanupFailed => {
155                "ATM_DAEMON_ADVISORY_SESSION_CLEANUP_FAILED"
156            }
157            Self::RemoteDeliveryOutcomeUnknown => "ATM_REMOTE_OUTCOME_UNKNOWN",
158            Self::AddressParseFailed => "ATM_ADDRESS_PARSE_FAILED",
159            Self::TeamUnavailable => "ATM_TEAM_UNAVAILABLE",
160            Self::TeamInvalid => "ATM_TEAM_INVALID",
161            Self::TeamNotFound => "ATM_TEAM_NOT_FOUND",
162            Self::AgentNotFound => "ATM_AGENT_NOT_FOUND",
163            _ => return None,
164        })
165    }
166
167    fn mailbox_or_validation_str(self) -> Option<&'static str> {
168        Some(match self {
169            Self::MailboxReadFailed => "ATM_MAILBOX_READ_FAILED",
170            Self::MailboxWriteFailed => "ATM_MAILBOX_WRITE_FAILED",
171            Self::MailboxLockFailed => "ATM_MAILBOX_LOCK_FAILED",
172            Self::MailboxLockReadOnlyFilesystem => "ATM_MAILBOX_LOCK_READ_ONLY_FILESYSTEM",
173            Self::MailboxLockTimeout => "ATM_MAILBOX_LOCK_TIMEOUT",
174            Self::InternalError => "ATM_INTERNAL_ERROR",
175            Self::MessageValidationFailed => "ATM_MESSAGE_VALIDATION_FAILED",
176            Self::SelfAddressedSendInvalid => "ATM_SELF_ADDRESSED_SEND_INVALID",
177            Self::EmptyNudgeTemplateBody => "ATM_NUDGE_TEMPLATE_BODY_EMPTY",
178            Self::CallerContextRequestInvalid => "ATM_CALLER_CONTEXT_REQUEST_INVALID",
179            Self::SerializationFailed => "ATM_SERIALIZATION_FAILED",
180            Self::FilePolicyRejected => "ATM_FILE_POLICY_REJECTED",
181            Self::FileReferenceRewriteFailed => "ATM_FILE_REFERENCE_REWRITE_FAILED",
182            Self::WaitTimeout => "ATM_WAIT_TIMEOUT",
183            Self::AckInvalidState => "ATM_ACK_INVALID_STATE",
184            Self::ClearInvalidState => "ATM_CLEAR_INVALID_STATE",
185            _ => return None,
186        })
187    }
188
189    fn observability_or_warning_str(self) -> Option<&'static str> {
190        Some(match self {
191            Self::ObservabilityEmitFailed => "ATM_OBSERVABILITY_EMIT_FAILED",
192            Self::ObservabilityQueryFailed => "ATM_OBSERVABILITY_QUERY_FAILED",
193            Self::ObservabilityFollowFailed => "ATM_OBSERVABILITY_FOLLOW_FAILED",
194            Self::ObservabilityHealthFailed => "ATM_OBSERVABILITY_HEALTH_FAILED",
195            Self::ObservabilityBootstrapFailed => "ATM_OBSERVABILITY_BOOTSTRAP_FAILED",
196            Self::ObservabilityHealthOk => "ATM_OBSERVABILITY_HEALTH_OK",
197            Self::WarningInvalidTeamMemberSkipped => "ATM_WARNING_INVALID_TEAM_MEMBER_SKIPPED",
198            Self::WarningMailboxRecordSkipped => "ATM_WARNING_MAILBOX_RECORD_SKIPPED",
199            Self::WarningMalformedAtmFieldIgnored => "ATM_WARNING_MALFORMED_ATM_FIELD_IGNORED",
200            Self::WarningObservabilityHealthDegraded => "ATM_WARNING_OBSERVABILITY_HEALTH_DEGRADED",
201            Self::WarningSqliteHealthDegraded => "ATM_WARNING_SQLITE_HEALTH_DEGRADED",
202            Self::WarningOriginInboxEntrySkipped => "ATM_WARNING_ORIGIN_INBOX_ENTRY_SKIPPED",
203            Self::WarningMissingTeamConfigFallback => "ATM_WARNING_MISSING_TEAM_CONFIG_FALLBACK",
204            Self::WarningSendAlertStateDegraded => "ATM_WARNING_SEND_ALERT_STATE_DEGRADED",
205            Self::WarningIdentityDrift => "ATM_WARNING_IDENTITY_DRIFT",
206            Self::WarningRosterDrift => "ATM_WARNING_ROSTER_DRIFT",
207            Self::WarningBaselineMemberMissing => "ATM_WARNING_BASELINE_MEMBER_MISSING",
208            Self::WarningRestoreInProgress => "ATM_WARNING_RESTORE_IN_PROGRESS",
209            Self::WarningStaleMailboxLock => "ATM_WARNING_STALE_MAILBOX_LOCK",
210            Self::WarningHookSkipped => "ATM_WARNING_HOOK_SKIPPED",
211            Self::WarningHookExecutionFailed => "ATM_WARNING_HOOK_EXECUTION_FAILED",
212            _ => return None,
213        })
214    }
215
216    fn post_send_or_misc_str(self) -> &'static str {
217        match self {
218            Self::PostSendPaneMissing => "ATM_POST_SEND_PANE_MISSING",
219            Self::PostSendTmuxSendFailed => "ATM_POST_SEND_TMUX_SEND_FAILED",
220            Self::PostSendGraftUnavailable => "ATM_POST_SEND_GRAFT_UNAVAILABLE",
221            Self::PostSendAdvisoryDeliveryFailed => "ATM_POST_SEND_ADVISORY_DELIVERY_FAILED",
222            Self::TestFakeTransportInjectionFailed => "ATM_TEST_FAKE_TRANSPORT_INJECTION_FAILED",
223            Self::HelpTopicNotFound => "ATM_HELP_TOPIC_NOT_FOUND",
224            _ => unreachable!("all AtmErrorCode variants must be covered by as_str helpers"),
225        }
226    }
227}
228
229impl FromStr for AtmErrorCode {
230    type Err = UnknownAtmErrorCode;
231
232    fn from_str(value: &str) -> Result<Self, Self::Err> {
233        parse_known_error_code(value).ok_or_else(|| UnknownAtmErrorCode(value.to_owned()))
234    }
235}
236
237fn parse_known_error_code(value: &str) -> Option<AtmErrorCode> {
238    parse_config_or_identity_code(value)
239        .or_else(|| parse_daemon_or_address_code(value))
240        .or_else(|| parse_mailbox_or_validation_code(value))
241        .or_else(|| parse_observability_or_warning_code(value))
242        .or_else(|| parse_post_send_or_misc_code(value))
243}
244
245fn parse_config_or_identity_code(value: &str) -> Option<AtmErrorCode> {
246    Some(match value {
247        "ATM_CONFIG_HOME_UNAVAILABLE" => AtmErrorCode::ConfigHomeUnavailable,
248        "ATM_HOME_UNRESOLVED" => AtmErrorCode::AtmHomeUnresolved,
249        "ATM_CONFIG_PARSE_FAILED" => AtmErrorCode::ConfigParseFailed,
250        "ATM_CONFIG_RETIRED_HOOK_MEMBERS_KEY" => AtmErrorCode::ConfigRetiredHookMembersKey,
251        "ATM_CONFIG_RETIRED_LEGACY_HOOK_KEYS" => AtmErrorCode::ConfigRetiredLegacyHookKeys,
252        "ATM_CONFIG_TEAM_PARSE_FAILED" => AtmErrorCode::ConfigTeamParseFailed,
253        "ATM_CONFIG_TEAM_MISSING" => AtmErrorCode::ConfigTeamMissing,
254        "ATM_IDENTITY_UNAVAILABLE" => AtmErrorCode::IdentityUnavailable,
255        "ATM_IDENTITY_INVALID" => AtmErrorCode::IdentityInvalid,
256        "ATM_IDENTITY_CONFLICT" => AtmErrorCode::IdentityConflict,
257        "ATM_MEMBER_ALREADY_EXISTS" => AtmErrorCode::MemberAlreadyExists,
258        "ATM_MEMBER_NOT_FOUND" => AtmErrorCode::MemberNotFound,
259        _ => return None,
260    })
261}
262
263fn parse_daemon_or_address_code(value: &str) -> Option<AtmErrorCode> {
264    Some(match value {
265        "ATM_DAEMON_UNAVAILABLE" => AtmErrorCode::DaemonUnavailable,
266        "ATM_RUNTIME_ROOT_INVALID" => AtmErrorCode::RuntimeRootInvalid,
267        "ATM_RUNTIME_BOOTSTRAP_REFUSED" => AtmErrorCode::RuntimeBootstrapRefused,
268        "ATM_SOCKET_OVERRIDE_FORBIDDEN" => AtmErrorCode::SocketOverrideForbidden,
269        "ATM_DAEMON_MAY_HAVE_EXECUTED" => AtmErrorCode::DaemonMayHaveExecuted,
270        "ATM_DAEMON_LIFECYCLE_WEDGE" => AtmErrorCode::DaemonLifecycleWedge,
271        "ATM_DAEMON_LAUNCH_GATE_REJECTED" => AtmErrorCode::DaemonLaunchGateRejected,
272        "ATM_DAEMON_SERVING_STATE_REJECTED" => AtmErrorCode::DaemonServingStateRejected,
273        "ATM_DAEMON_STALE_OWNER_RECOVERY_FAILED" => AtmErrorCode::DaemonStaleOwnerRecoveryFailed,
274        "ATM_DAEMON_AUTO_START_FAILED" => AtmErrorCode::DaemonAutoStartFailed,
275        "ATM_DAEMON_CONNECTION_SATURATED" => AtmErrorCode::DaemonConnectionSaturated,
276        "ATM_CLIENT_DAEMON_VERSION_INCOMPATIBLE" => AtmErrorCode::ClientDaemonVersionIncompatible,
277        "ATM_DAEMON_ADVISORY_SESSION_ALREADY_REGISTERED" => {
278            AtmErrorCode::DaemonAdvisorySessionAlreadyRegistered
279        }
280        "ATM_DAEMON_ADVISORY_SESSION_NOT_REGISTERED" => {
281            AtmErrorCode::DaemonAdvisorySessionNotRegistered
282        }
283        "ATM_DAEMON_ADVISORY_SESSION_CLEANUP_FAILED" => {
284            AtmErrorCode::DaemonAdvisorySessionCleanupFailed
285        }
286        "ATM_REMOTE_OUTCOME_UNKNOWN" => AtmErrorCode::RemoteDeliveryOutcomeUnknown,
287        "ATM_ADDRESS_PARSE_FAILED" => AtmErrorCode::AddressParseFailed,
288        "ATM_TEAM_UNAVAILABLE" => AtmErrorCode::TeamUnavailable,
289        "ATM_TEAM_INVALID" => AtmErrorCode::TeamInvalid,
290        "ATM_TEAM_NOT_FOUND" => AtmErrorCode::TeamNotFound,
291        "ATM_AGENT_NOT_FOUND" => AtmErrorCode::AgentNotFound,
292        _ => return None,
293    })
294}
295
296fn parse_mailbox_or_validation_code(value: &str) -> Option<AtmErrorCode> {
297    Some(match value {
298        "ATM_MAILBOX_READ_FAILED" => AtmErrorCode::MailboxReadFailed,
299        "ATM_MAILBOX_WRITE_FAILED" => AtmErrorCode::MailboxWriteFailed,
300        "ATM_MAILBOX_LOCK_FAILED" => AtmErrorCode::MailboxLockFailed,
301        "ATM_MAILBOX_LOCK_READ_ONLY_FILESYSTEM" => AtmErrorCode::MailboxLockReadOnlyFilesystem,
302        "ATM_MAILBOX_LOCK_TIMEOUT" => AtmErrorCode::MailboxLockTimeout,
303        "ATM_INTERNAL_ERROR" => AtmErrorCode::InternalError,
304        "ATM_MESSAGE_VALIDATION_FAILED" => AtmErrorCode::MessageValidationFailed,
305        "ATM_SELF_ADDRESSED_SEND_INVALID" => AtmErrorCode::SelfAddressedSendInvalid,
306        "ATM_NUDGE_TEMPLATE_BODY_EMPTY" => AtmErrorCode::EmptyNudgeTemplateBody,
307        "ATM_CALLER_CONTEXT_REQUEST_INVALID" => AtmErrorCode::CallerContextRequestInvalid,
308        "ATM_SERIALIZATION_FAILED" => AtmErrorCode::SerializationFailed,
309        "ATM_FILE_POLICY_REJECTED" => AtmErrorCode::FilePolicyRejected,
310        "ATM_FILE_REFERENCE_REWRITE_FAILED" => AtmErrorCode::FileReferenceRewriteFailed,
311        "ATM_WAIT_TIMEOUT" => AtmErrorCode::WaitTimeout,
312        "ATM_ACK_INVALID_STATE" => AtmErrorCode::AckInvalidState,
313        "ATM_CLEAR_INVALID_STATE" => AtmErrorCode::ClearInvalidState,
314        _ => return None,
315    })
316}
317
318fn parse_observability_or_warning_code(value: &str) -> Option<AtmErrorCode> {
319    Some(match value {
320        "ATM_OBSERVABILITY_EMIT_FAILED" => AtmErrorCode::ObservabilityEmitFailed,
321        "ATM_OBSERVABILITY_QUERY_FAILED" => AtmErrorCode::ObservabilityQueryFailed,
322        "ATM_OBSERVABILITY_FOLLOW_FAILED" => AtmErrorCode::ObservabilityFollowFailed,
323        "ATM_OBSERVABILITY_HEALTH_FAILED" => AtmErrorCode::ObservabilityHealthFailed,
324        "ATM_OBSERVABILITY_BOOTSTRAP_FAILED" => AtmErrorCode::ObservabilityBootstrapFailed,
325        "ATM_OBSERVABILITY_HEALTH_OK" => AtmErrorCode::ObservabilityHealthOk,
326        "ATM_WARNING_INVALID_TEAM_MEMBER_SKIPPED" => AtmErrorCode::WarningInvalidTeamMemberSkipped,
327        "ATM_WARNING_MAILBOX_RECORD_SKIPPED" => AtmErrorCode::WarningMailboxRecordSkipped,
328        "ATM_WARNING_MALFORMED_ATM_FIELD_IGNORED" => AtmErrorCode::WarningMalformedAtmFieldIgnored,
329        "ATM_WARNING_OBSERVABILITY_HEALTH_DEGRADED" => {
330            AtmErrorCode::WarningObservabilityHealthDegraded
331        }
332        "ATM_WARNING_SQLITE_HEALTH_DEGRADED" => AtmErrorCode::WarningSqliteHealthDegraded,
333        "ATM_WARNING_ORIGIN_INBOX_ENTRY_SKIPPED" => AtmErrorCode::WarningOriginInboxEntrySkipped,
334        "ATM_WARNING_MISSING_TEAM_CONFIG_FALLBACK" => {
335            AtmErrorCode::WarningMissingTeamConfigFallback
336        }
337        "ATM_WARNING_SEND_ALERT_STATE_DEGRADED" => AtmErrorCode::WarningSendAlertStateDegraded,
338        "ATM_WARNING_IDENTITY_DRIFT" => AtmErrorCode::WarningIdentityDrift,
339        "ATM_WARNING_ROSTER_DRIFT" => AtmErrorCode::WarningRosterDrift,
340        "ATM_WARNING_BASELINE_MEMBER_MISSING" => AtmErrorCode::WarningBaselineMemberMissing,
341        "ATM_WARNING_RESTORE_IN_PROGRESS" => AtmErrorCode::WarningRestoreInProgress,
342        "ATM_WARNING_STALE_MAILBOX_LOCK" => AtmErrorCode::WarningStaleMailboxLock,
343        "ATM_WARNING_HOOK_SKIPPED" => AtmErrorCode::WarningHookSkipped,
344        "ATM_WARNING_HOOK_EXECUTION_FAILED" => AtmErrorCode::WarningHookExecutionFailed,
345        _ => return None,
346    })
347}
348
349fn parse_post_send_or_misc_code(value: &str) -> Option<AtmErrorCode> {
350    Some(match value {
351        "ATM_POST_SEND_PANE_MISSING" => AtmErrorCode::PostSendPaneMissing,
352        "ATM_POST_SEND_TMUX_SEND_FAILED" => AtmErrorCode::PostSendTmuxSendFailed,
353        "ATM_POST_SEND_GRAFT_UNAVAILABLE" => AtmErrorCode::PostSendGraftUnavailable,
354        "ATM_POST_SEND_ADVISORY_DELIVERY_FAILED" => AtmErrorCode::PostSendAdvisoryDeliveryFailed,
355        "ATM_TEST_FAKE_TRANSPORT_INJECTION_FAILED" => {
356            AtmErrorCode::TestFakeTransportInjectionFailed
357        }
358        "ATM_HELP_TOPIC_NOT_FOUND" => AtmErrorCode::HelpTopicNotFound,
359        _ => return None,
360    })
361}
362
363impl fmt::Display for AtmErrorCode {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        f.write_str(self.as_str())
366    }
367}
368
369impl Serialize for AtmErrorCode {
370    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
371    where
372        S: serde::Serializer,
373    {
374        serializer.serialize_str(self.as_str())
375    }
376}
377
378impl<'de> Deserialize<'de> for AtmErrorCode {
379    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
380    where
381        D: Deserializer<'de>,
382    {
383        let value = String::deserialize(deserializer)?;
384        value.parse().map_err(serde::de::Error::custom)
385    }
386}