1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct InvalidIdentity {
13 pub input: String,
14 pub reason: String,
15}
16
17impl fmt::Display for InvalidIdentity {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 write!(f, "invalid identity {:?}: {}", self.input, self.reason)
20 }
21}
22
23impl std::error::Error for InvalidIdentity {}
24
25fn validate_identity_string(s: &str) -> Result<(), InvalidIdentity> {
26 if s.is_empty() {
27 return Err(InvalidIdentity {
28 input: s.to_string(),
29 reason: "must not be empty".to_string(),
30 });
31 }
32 if s.contains(char::is_whitespace) {
33 return Err(InvalidIdentity {
34 input: s.to_string(),
35 reason: "must not contain whitespace".to_string(),
36 });
37 }
38 if s.contains('/') {
39 return Err(InvalidIdentity {
40 input: s.to_string(),
41 reason: "must not contain slashes".to_string(),
42 });
43 }
44 Ok(())
45}
46
47macro_rules! validated_string_newtype {
52 ($(#[$meta:meta])* $name:ident) => {
53 $(#[$meta])*
54 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
55 pub struct $name(String);
56
57 impl $name {
58 pub fn parse(s: &str) -> Result<Self, InvalidIdentity> {
65 validate_identity_string(s)?;
66 Ok(Self(s.to_string()))
67 }
68
69 #[must_use]
70 pub fn as_str(&self) -> &str {
71 &self.0
72 }
73 }
74
75 impl fmt::Display for $name {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 f.write_str(&self.0)
78 }
79 }
80
81 impl Serialize for $name {
82 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
83 where
84 S: serde::Serializer,
85 {
86 serializer.serialize_str(&self.0)
87 }
88 }
89
90 impl<'de> Deserialize<'de> for $name {
91 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92 where
93 D: serde::Deserializer<'de>,
94 {
95 let s = String::deserialize(deserializer)?;
96 Self::parse(&s).map_err(serde::de::Error::custom)
97 }
98 }
99 };
100}
101
102validated_string_newtype!(
103 AgentIdentity
105);
106
107validated_string_newtype!(
108 AgentRuntimeId
110);
111
112#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
118#[serde(rename_all = "snake_case")]
119pub enum AgentAddressability {
120 #[default]
121 Addressable,
122 InternalOnly,
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash)]
131pub struct DisplayName(String);
132
133impl DisplayName {
134 pub fn parse(s: &str) -> Result<Self, InvalidIdentity> {
138 if s.is_empty() {
139 return Err(InvalidIdentity {
140 input: s.to_string(),
141 reason: "display name must not be empty".to_string(),
142 });
143 }
144 Ok(Self(s.to_string()))
145 }
146
147 #[must_use]
148 pub fn as_str(&self) -> &str {
149 &self.0
150 }
151}
152
153impl fmt::Display for DisplayName {
154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155 f.write_str(&self.0)
156 }
157}
158
159impl Serialize for DisplayName {
160 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
161 where
162 S: serde::Serializer,
163 {
164 serializer.serialize_str(&self.0)
165 }
166}
167
168impl<'de> Deserialize<'de> for DisplayName {
169 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
170 where
171 D: serde::Deserializer<'de>,
172 {
173 let s = String::deserialize(deserializer)?;
174 Self::parse(&s).map_err(serde::de::Error::custom)
175 }
176}
177
178macro_rules! monotonic_u64_newtype {
183 ($(#[$meta:meta])* $name:ident) => {
184 $(#[$meta])*
185 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
186 #[serde(transparent)]
187 pub struct $name(u64);
188
189 impl $name {
190 #[must_use]
191 pub const fn new(value: u64) -> Self {
192 Self(value)
193 }
194
195 #[must_use]
196 pub const fn get(self) -> u64 {
197 self.0
198 }
199 }
200
201 impl fmt::Display for $name {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f, "{}", self.0)
204 }
205 }
206 };
207}
208
209monotonic_u64_newtype!(
210 ContinuityGeneration
212);
213
214monotonic_u64_newtype!(
215 CheckpointVersion
217);
218
219monotonic_u64_newtype!(
220 FencingToken
222);
223
224macro_rules! string_newtype {
229 ($(#[$meta:meta])* $name:ident) => {
230 $(#[$meta])*
231 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
232 #[serde(transparent)]
233 pub struct $name(String);
234
235 impl $name {
236 #[must_use]
237 pub fn new(s: impl Into<String>) -> Self {
238 Self(s.into())
239 }
240
241 #[must_use]
242 pub fn as_str(&self) -> &str {
243 &self.0
244 }
245 }
246
247 impl fmt::Display for $name {
248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
249 f.write_str(&self.0)
250 }
251 }
252 };
253}
254
255string_newtype!(
256 CorrelationId
258);
259
260string_newtype!(
261 DispatchIdempotencyKey
263);
264
265#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
271pub struct ContinuityRecord {
272 pub identity: AgentIdentity,
273 pub agent_runtime_id: AgentRuntimeId,
274 pub session_id: meerkat_core::types::SessionId,
275 pub generation: ContinuityGeneration,
276 pub checkpoint_version: CheckpointVersion,
277}
278
279#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
285#[serde(rename_all = "snake_case")]
286pub enum ContinuityFailureKind {
287 SnapshotMissing,
288 SnapshotCorrupted,
289 GenerationMismatch,
290 StoreUnavailable,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
295pub struct ContinuityFailure {
296 pub identity: AgentIdentity,
297 pub kind: ContinuityFailureKind,
298 pub record: Option<ContinuityRecord>,
299 pub detail: String,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case", tag = "state")]
309pub enum ContinuityResolveState {
310 Uninitialized,
311 Ready { record: ContinuityRecord },
312 Broken { failure: ContinuityFailure },
313}
314
315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
321pub struct LeaseGrant {
322 pub identity: AgentIdentity,
323 pub fencing_token: FencingToken,
324 #[serde(
325 serialize_with = "serde_duration_ms::serialize",
326 deserialize_with = "serde_duration_ms::deserialize"
327 )]
328 pub ttl: std::time::Duration,
329}
330
331mod serde_duration_ms {
333 use serde::{Deserialize, Deserializer, Serializer};
334 use std::time::Duration;
335
336 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
337 where
338 S: Serializer,
339 {
340 let ms = duration.as_millis();
341 serializer.serialize_u64(ms as u64)
343 }
344
345 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
346 where
347 D: Deserializer<'de>,
348 {
349 let ms = u64::deserialize(deserializer)?;
350 Ok(Duration::from_millis(ms))
351 }
352}
353
354#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
360#[serde(rename_all = "snake_case", tag = "result")]
361pub enum LeaseAcquireResult {
362 Acquired(LeaseGrant),
363 AlreadyHeld {
364 identity: AgentIdentity,
365 holder: String,
366 },
367}
368
369#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
371#[serde(rename_all = "snake_case", tag = "result")]
372pub enum LeaseRenewResult {
373 Renewed(LeaseGrant),
374 Lost { identity: AgentIdentity },
375}
376
377#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
383#[serde(rename_all = "snake_case")]
384pub enum DispatchOrigin {
385 Connector,
386 Scheduler,
387 Policy,
388 Flow,
389 System,
390}
391
392#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
394pub struct DispatchInput {
395 pub content: meerkat_core::ContentInput,
396 pub origin: DispatchOrigin,
397 pub correlation_id: Option<CorrelationId>,
398 pub idempotency_key: Option<DispatchIdempotencyKey>,
399}
400
401impl DispatchInput {
402 pub fn system(text: impl Into<String>) -> Self {
404 Self {
405 content: meerkat_core::ContentInput::Text(text.into()),
406 origin: DispatchOrigin::System,
407 correlation_id: None,
408 idempotency_key: None,
409 }
410 }
411
412 pub fn with_origin(text: impl Into<String>, origin: DispatchOrigin) -> Self {
414 Self {
415 content: meerkat_core::ContentInput::Text(text.into()),
416 origin,
417 correlation_id: None,
418 idempotency_key: None,
419 }
420 }
421
422 pub fn with_correlation(mut self, id: impl Into<String>) -> Self {
424 self.correlation_id = Some(CorrelationId::new(id));
425 self
426 }
427
428 pub fn with_idempotency(mut self, key: impl Into<String>) -> Self {
430 self.idempotency_key = Some(DispatchIdempotencyKey::new(key));
431 self
432 }
433}
434
435#[derive(Debug, Clone, PartialEq, Eq)]
441pub enum ManagedPeerEdgeError {
442 SelfEdge,
443}
444
445impl fmt::Display for ManagedPeerEdgeError {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 match self {
448 Self::SelfEdge => write!(f, "self-edges are not allowed"),
449 }
450 }
451}
452
453impl std::error::Error for ManagedPeerEdgeError {}
454
455#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
460#[serde(into = "ManagedPeerEdgeRaw")]
461pub struct ManagedPeerEdge {
462 a: AgentIdentity,
463 b: AgentIdentity,
464}
465
466#[derive(Serialize, Deserialize)]
468struct ManagedPeerEdgeRaw {
469 a: AgentIdentity,
470 b: AgentIdentity,
471}
472
473impl From<ManagedPeerEdge> for ManagedPeerEdgeRaw {
474 fn from(edge: ManagedPeerEdge) -> Self {
475 Self {
476 a: edge.a,
477 b: edge.b,
478 }
479 }
480}
481
482impl TryFrom<ManagedPeerEdgeRaw> for ManagedPeerEdge {
483 type Error = ManagedPeerEdgeError;
484
485 fn try_from(raw: ManagedPeerEdgeRaw) -> Result<Self, Self::Error> {
486 Self::new(raw.a, raw.b)
487 }
488}
489
490impl<'de> Deserialize<'de> for ManagedPeerEdge {
491 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
492 where
493 D: serde::Deserializer<'de>,
494 {
495 let raw = ManagedPeerEdgeRaw::deserialize(deserializer)?;
496 Self::try_from(raw).map_err(serde::de::Error::custom)
497 }
498}
499
500impl ManagedPeerEdge {
501 pub fn new(a: AgentIdentity, b: AgentIdentity) -> Result<Self, ManagedPeerEdgeError> {
507 if a == b {
508 return Err(ManagedPeerEdgeError::SelfEdge);
509 }
510 if a < b {
511 Ok(Self { a, b })
512 } else {
513 Ok(Self { a: b, b: a })
514 }
515 }
516
517 #[must_use]
518 pub fn a(&self) -> &AgentIdentity {
519 &self.a
520 }
521
522 #[must_use]
523 pub fn b(&self) -> &AgentIdentity {
524 &self.b
525 }
526}
527
528#[derive(Debug, Clone, PartialEq, Eq)]
534pub struct NotAddressable {
535 pub identity: AgentIdentity,
536 pub addressability: AgentAddressability,
537}
538
539impl fmt::Display for NotAddressable {
540 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
541 write!(
542 f,
543 "agent {:?} is not addressable (current: {:?})",
544 self.identity, self.addressability
545 )
546 }
547}
548
549impl std::error::Error for NotAddressable {}
550
551#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
557pub struct DurableAgentSpec {
558 pub identity: AgentIdentity,
559 pub profile: meerkat_mob::ProfileName,
560 #[serde(default)]
561 pub addressability: AgentAddressability,
562 pub display_name: Option<DisplayName>,
563 #[serde(default)]
564 pub labels: std::collections::BTreeMap<String, String>,
565 pub context: Option<serde_json::Value>,
566 #[serde(default)]
567 pub additional_instructions: Vec<String>,
568 #[serde(default)]
569 pub initial_message: Option<meerkat_core::ContentInput>,
570 #[serde(default)]
571 pub runtime_mode_override: Option<meerkat_mob::MobRuntimeMode>,
572 #[serde(default)]
573 pub backend: Option<meerkat_mob::MobBackendKind>,
574 #[serde(default)]
575 pub binding: Option<meerkat_contracts::WireRuntimeBinding>,
576}
577
578#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
584#[serde(rename_all = "snake_case")]
585pub enum IdentityLifecycleState {
586 Dormant,
589 Active,
591 Retiring,
592 Suspended,
593 Broken,
595 Uninitialized,
596}
597
598impl IdentityLifecycleState {
599 pub fn wire_str(self) -> &'static str {
608 match self {
609 Self::Dormant => "dormant",
610 Self::Active => "active",
611 Self::Retiring => "retiring",
612 Self::Suspended => "suspended",
613 Self::Broken => "broken",
614 Self::Uninitialized => "uninitialized",
615 }
616 }
617}
618
619#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
621pub struct LeaseInfo {
622 pub fencing_token: FencingToken,
623 #[serde(
624 serialize_with = "serde_duration_ms::serialize",
625 deserialize_with = "serde_duration_ms::deserialize"
626 )]
627 pub ttl_remaining: std::time::Duration,
628 pub healthy: bool,
629}
630
631#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
633#[serde(rename_all = "snake_case", tag = "kind")]
634pub enum DurabilityPolicy {
635 SyncWriteThrough,
636 AsyncReplicated,
637 BufferedExport { max_loss_window_ms: u64 },
638}
639
640#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
642pub struct ContinuityHealth {
643 pub store_reachable: bool,
644 pub durability_policy: DurabilityPolicy,
645 pub last_checkpoint_version: Option<CheckpointVersion>,
646}
647
648#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
650pub struct IdentityStatus {
651 pub identity: AgentIdentity,
652 pub state: IdentityLifecycleState,
653 pub agent_runtime_id: Option<AgentRuntimeId>,
654 pub session_id: Option<meerkat_core::types::SessionId>,
655 pub profile: Option<meerkat_mob::ProfileName>,
656 pub runtime_mode: Option<meerkat_mob::MobRuntimeMode>,
657 pub addressability: AgentAddressability,
658 pub display_name: Option<DisplayName>,
659 #[serde(default)]
660 pub labels: std::collections::BTreeMap<String, String>,
661 pub generation: Option<ContinuityGeneration>,
662 pub checkpoint_version: Option<CheckpointVersion>,
663 pub lease: Option<LeaseInfo>,
664 pub continuity_health: Option<ContinuityHealth>,
665}
666
667#[derive(Clone, Default)]
672pub struct AgentRuntimeServices {
673 mob_handle: Option<meerkat_mob::MobHandle>,
674}
675
676impl AgentRuntimeServices {
677 pub fn new(mob_handle: meerkat_mob::MobHandle) -> Self {
678 Self {
679 mob_handle: Some(mob_handle),
680 }
681 }
682
683 pub fn empty() -> Self {
684 Self { mob_handle: None }
685 }
686
687 pub fn mob_handle(&self) -> Option<meerkat_mob::MobHandle> {
688 self.mob_handle.clone()
689 }
690
691 pub fn has_mob_handle(&self) -> bool {
692 self.mob_handle.is_some()
693 }
694}
695
696impl std::fmt::Debug for AgentRuntimeServices {
697 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698 f.debug_struct("AgentRuntimeServices")
699 .field("mob_handle", &self.mob_handle.is_some())
700 .finish()
701 }
702}
703
704impl PartialEq for AgentRuntimeServices {
705 fn eq(&self, other: &Self) -> bool {
706 self.mob_handle.is_some() == other.mob_handle.is_some()
707 }
708}
709
710impl Eq for AgentRuntimeServices {}
711
712#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
714pub struct AgentBuildContext {
715 pub identity: AgentIdentity,
716 pub active_peers: Vec<AgentIdentity>,
717 pub managed_edges: Vec<ManagedPeerEdge>,
718 #[serde(default, skip)]
719 pub runtime_services: AgentRuntimeServices,
720}
721
722#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
724pub struct ExternalToolDef {
725 pub name: String,
726 pub description: String,
727 pub input_schema: serde_json::Value,
728}
729
730#[derive(Clone, Default)]
732pub struct LocalExternalToolOverlay {
733 dispatcher: Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>>,
734}
735
736impl LocalExternalToolOverlay {
737 pub fn new(dispatcher: Arc<dyn meerkat_core::agent::AgentToolDispatcher>) -> Self {
738 Self {
739 dispatcher: Some(dispatcher),
740 }
741 }
742
743 pub fn empty() -> Self {
744 Self { dispatcher: None }
745 }
746
747 pub fn dispatcher(&self) -> Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>> {
748 self.dispatcher.clone()
749 }
750
751 pub fn is_some(&self) -> bool {
752 self.dispatcher.is_some()
753 }
754}
755
756impl std::fmt::Debug for LocalExternalToolOverlay {
757 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
758 f.debug_struct("LocalExternalToolOverlay")
759 .field("dispatcher", &self.dispatcher.is_some())
760 .finish()
761 }
762}
763
764impl PartialEq for LocalExternalToolOverlay {
765 fn eq(&self, other: &Self) -> bool {
766 self.dispatcher.is_some() == other.dispatcher.is_some()
767 }
768}
769
770impl Eq for LocalExternalToolOverlay {}
771
772#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
778pub struct AgentBuildDraft {
779 pub model: Option<String>,
780 pub system_prompt: Option<String>,
781 #[serde(default)]
782 pub additional_instructions: Vec<String>,
783 #[serde(default)]
784 pub labels: std::collections::BTreeMap<String, String>,
785 pub app_context: Option<serde_json::Value>,
786 #[serde(default)]
787 pub external_tools: Vec<ExternalToolDef>,
788 #[serde(default, skip)]
789 pub local_external_tools: LocalExternalToolOverlay,
790}
791
792#[derive(Debug, Clone, PartialEq, Eq)]
801pub struct SessionSnapshot {
802 pub data: Vec<u8>,
803}
804
805impl Serialize for SessionSnapshot {
806 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
807 where
808 S: serde::Serializer,
809 {
810 use base64::Engine;
811 use serde::ser::SerializeStruct;
812 let encoded = base64::engine::general_purpose::STANDARD.encode(&self.data);
813 let mut s = serializer.serialize_struct("SessionSnapshot", 1)?;
814 s.serialize_field("data", &encoded)?;
815 s.end()
816 }
817}
818
819impl<'de> Deserialize<'de> for SessionSnapshot {
820 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
821 where
822 D: serde::Deserializer<'de>,
823 {
824 use base64::Engine;
825
826 #[derive(Deserialize)]
827 struct Wrapper {
828 data: String,
829 }
830
831 let wrapper = Wrapper::deserialize(deserializer)?;
832 let data = base64::engine::general_purpose::STANDARD
833 .decode(&wrapper.data)
834 .map_err(serde::de::Error::custom)?;
835 Ok(Self { data })
836 }
837}
838
839#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
845pub struct RosterContext {
846 pub mob_definition: Option<meerkat_mob::MobDefinition>,
847 pub previous_identities: Vec<AgentIdentity>,
848}
849
850#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
852pub struct TopologyContext {
853 pub roster: Vec<DurableAgentSpec>,
854}
855
856#[derive(Debug)]
862pub enum ContinuityStoreError {
863 StaleFencingToken {
864 identity: AgentIdentity,
865 presented: FencingToken,
866 current: FencingToken,
867 },
868 StaleCheckpointVersion {
869 identity: AgentIdentity,
870 presented: CheckpointVersion,
871 current: CheckpointVersion,
872 },
873 NotFound {
874 identity: AgentIdentity,
875 },
876 Io(String),
877 Corruption(String),
878}
879
880impl fmt::Display for ContinuityStoreError {
881 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
882 match self {
883 Self::StaleFencingToken {
884 identity,
885 presented,
886 current,
887 } => write!(
888 f,
889 "stale fencing token for {identity}: presented {presented}, current {current}"
890 ),
891 Self::StaleCheckpointVersion {
892 identity,
893 presented,
894 current,
895 } => write!(
896 f,
897 "stale checkpoint version for {identity}: presented {presented}, current {current}"
898 ),
899 Self::NotFound { identity } => {
900 write!(f, "continuity record not found for {identity}")
901 }
902 Self::Io(msg) => write!(f, "continuity store I/O error: {msg}"),
903 Self::Corruption(msg) => write!(f, "continuity store corruption: {msg}"),
904 }
905 }
906}
907
908impl std::error::Error for ContinuityStoreError {}
909
910#[derive(Debug)]
912pub enum LeaseError {
913 ProviderUnavailable(String),
914 Io(String),
915}
916
917impl fmt::Display for LeaseError {
918 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
919 match self {
920 Self::ProviderUnavailable(msg) => {
921 write!(f, "lease provider unavailable: {msg}")
922 }
923 Self::Io(msg) => write!(f, "lease I/O error: {msg}"),
924 }
925 }
926}
927
928impl std::error::Error for LeaseError {}
929
930#[derive(Debug)]
932pub enum RosterError {
933 ProviderUnavailable(String),
934 Io(String),
935}
936
937impl fmt::Display for RosterError {
938 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
939 match self {
940 Self::ProviderUnavailable(msg) => {
941 write!(f, "roster provider unavailable: {msg}")
942 }
943 Self::Io(msg) => write!(f, "roster I/O error: {msg}"),
944 }
945 }
946}
947
948impl std::error::Error for RosterError {}
949
950#[derive(Debug)]
952pub enum TopologyError {
953 InvalidEdge(String),
954 ProviderUnavailable(String),
955}
956
957impl fmt::Display for TopologyError {
958 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
959 match self {
960 Self::InvalidEdge(msg) => write!(f, "invalid topology edge: {msg}"),
961 Self::ProviderUnavailable(msg) => {
962 write!(f, "topology provider unavailable: {msg}")
963 }
964 }
965 }
966}
967
968impl std::error::Error for TopologyError {}
969
970#[derive(Debug)]
972pub enum CustomizerError {
973 BuildFailed(String),
974 Io(String),
975}
976
977impl fmt::Display for CustomizerError {
978 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
979 match self {
980 Self::BuildFailed(msg) => write!(f, "customizer build failed: {msg}"),
981 Self::Io(msg) => write!(f, "customizer I/O error: {msg}"),
982 }
983 }
984}
985
986impl std::error::Error for CustomizerError {}