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 ResumeRejected,
295}
296
297#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
299pub struct ContinuityFailure {
300 pub identity: AgentIdentity,
301 pub kind: ContinuityFailureKind,
302 pub record: Option<ContinuityRecord>,
303 pub detail: String,
304}
305
306#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
312#[serde(rename_all = "snake_case", tag = "state")]
313pub enum ContinuityResolveState {
314 Uninitialized,
315 Ready { record: ContinuityRecord },
316 Broken { failure: ContinuityFailure },
317}
318
319#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
325pub struct LeaseGrant {
326 pub identity: AgentIdentity,
327 pub fencing_token: FencingToken,
328 #[serde(
329 serialize_with = "serde_duration_ms::serialize",
330 deserialize_with = "serde_duration_ms::deserialize"
331 )]
332 pub ttl: std::time::Duration,
333}
334
335mod serde_duration_ms {
337 use serde::{Deserialize, Deserializer, Serializer};
338 use std::time::Duration;
339
340 pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
341 where
342 S: Serializer,
343 {
344 let ms = duration.as_millis();
345 serializer.serialize_u64(ms as u64)
347 }
348
349 pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
350 where
351 D: Deserializer<'de>,
352 {
353 let ms = u64::deserialize(deserializer)?;
354 Ok(Duration::from_millis(ms))
355 }
356}
357
358#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
364#[serde(rename_all = "snake_case", tag = "result")]
365pub enum LeaseAcquireResult {
366 Acquired(LeaseGrant),
367 AlreadyHeld {
368 identity: AgentIdentity,
369 holder: String,
370 },
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
375#[serde(rename_all = "snake_case", tag = "result")]
376pub enum LeaseRenewResult {
377 Renewed(LeaseGrant),
378 Lost { identity: AgentIdentity },
379}
380
381#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
387#[serde(rename_all = "snake_case")]
388pub enum DispatchOrigin {
389 Connector,
390 Scheduler,
391 Policy,
392 Flow,
393 System,
394}
395
396#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
398pub struct DispatchInput {
399 pub content: meerkat_core::ContentInput,
400 pub origin: DispatchOrigin,
401 pub correlation_id: Option<CorrelationId>,
402 pub idempotency_key: Option<DispatchIdempotencyKey>,
403}
404
405impl DispatchInput {
406 pub fn system(text: impl Into<String>) -> Self {
408 Self {
409 content: meerkat_core::ContentInput::Text(text.into()),
410 origin: DispatchOrigin::System,
411 correlation_id: None,
412 idempotency_key: None,
413 }
414 }
415
416 pub fn with_origin(text: impl Into<String>, origin: DispatchOrigin) -> Self {
418 Self {
419 content: meerkat_core::ContentInput::Text(text.into()),
420 origin,
421 correlation_id: None,
422 idempotency_key: None,
423 }
424 }
425
426 pub fn with_correlation(mut self, id: impl Into<String>) -> Self {
428 self.correlation_id = Some(CorrelationId::new(id));
429 self
430 }
431
432 pub fn with_idempotency(mut self, key: impl Into<String>) -> Self {
434 self.idempotency_key = Some(DispatchIdempotencyKey::new(key));
435 self
436 }
437}
438
439#[derive(Debug, Clone, PartialEq, Eq)]
445pub enum ManagedPeerEdgeError {
446 SelfEdge,
447}
448
449impl fmt::Display for ManagedPeerEdgeError {
450 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
451 match self {
452 Self::SelfEdge => write!(f, "self-edges are not allowed"),
453 }
454 }
455}
456
457impl std::error::Error for ManagedPeerEdgeError {}
458
459#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
464#[serde(into = "ManagedPeerEdgeRaw")]
465pub struct ManagedPeerEdge {
466 a: AgentIdentity,
467 b: AgentIdentity,
468}
469
470#[derive(Serialize, Deserialize)]
472struct ManagedPeerEdgeRaw {
473 a: AgentIdentity,
474 b: AgentIdentity,
475}
476
477impl From<ManagedPeerEdge> for ManagedPeerEdgeRaw {
478 fn from(edge: ManagedPeerEdge) -> Self {
479 Self {
480 a: edge.a,
481 b: edge.b,
482 }
483 }
484}
485
486impl TryFrom<ManagedPeerEdgeRaw> for ManagedPeerEdge {
487 type Error = ManagedPeerEdgeError;
488
489 fn try_from(raw: ManagedPeerEdgeRaw) -> Result<Self, Self::Error> {
490 Self::new(raw.a, raw.b)
491 }
492}
493
494impl<'de> Deserialize<'de> for ManagedPeerEdge {
495 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
496 where
497 D: serde::Deserializer<'de>,
498 {
499 let raw = ManagedPeerEdgeRaw::deserialize(deserializer)?;
500 Self::try_from(raw).map_err(serde::de::Error::custom)
501 }
502}
503
504impl ManagedPeerEdge {
505 pub fn new(a: AgentIdentity, b: AgentIdentity) -> Result<Self, ManagedPeerEdgeError> {
511 if a == b {
512 return Err(ManagedPeerEdgeError::SelfEdge);
513 }
514 if a < b {
515 Ok(Self { a, b })
516 } else {
517 Ok(Self { a: b, b: a })
518 }
519 }
520
521 #[must_use]
522 pub fn a(&self) -> &AgentIdentity {
523 &self.a
524 }
525
526 #[must_use]
527 pub fn b(&self) -> &AgentIdentity {
528 &self.b
529 }
530}
531
532#[derive(Debug, Clone, PartialEq, Eq)]
538pub struct NotAddressable {
539 pub identity: AgentIdentity,
540 pub addressability: AgentAddressability,
541}
542
543impl fmt::Display for NotAddressable {
544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545 write!(
546 f,
547 "agent {:?} is not addressable (current: {:?})",
548 self.identity, self.addressability
549 )
550 }
551}
552
553impl std::error::Error for NotAddressable {}
554
555#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
561pub struct DurableAgentSpec {
562 pub identity: AgentIdentity,
563 pub profile: meerkat_mob::ProfileName,
564 #[serde(default)]
565 pub addressability: AgentAddressability,
566 pub display_name: Option<DisplayName>,
567 #[serde(default)]
568 pub labels: std::collections::BTreeMap<String, String>,
569 pub context: Option<serde_json::Value>,
570 #[serde(default)]
571 pub additional_instructions: Vec<String>,
572 #[serde(default)]
573 pub initial_message: Option<meerkat_core::ContentInput>,
574 #[serde(default)]
575 pub runtime_mode_override: Option<meerkat_mob::MobRuntimeMode>,
576 #[serde(default)]
577 pub backend: Option<meerkat_mob::MobBackendKind>,
578 #[serde(default)]
579 pub binding: Option<meerkat_contracts::WireRuntimeBinding>,
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
588#[serde(rename_all = "snake_case")]
589pub enum IdentityLifecycleState {
590 Dormant,
593 Active,
595 Retiring,
596 Suspended,
597 Broken,
599 Uninitialized,
600}
601
602impl IdentityLifecycleState {
603 pub fn wire_str(self) -> &'static str {
612 match self {
613 Self::Dormant => "dormant",
614 Self::Active => "active",
615 Self::Retiring => "retiring",
616 Self::Suspended => "suspended",
617 Self::Broken => "broken",
618 Self::Uninitialized => "uninitialized",
619 }
620 }
621}
622
623#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
625pub struct LeaseInfo {
626 pub fencing_token: FencingToken,
627 #[serde(
628 serialize_with = "serde_duration_ms::serialize",
629 deserialize_with = "serde_duration_ms::deserialize"
630 )]
631 pub ttl_remaining: std::time::Duration,
632 pub healthy: bool,
633}
634
635#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
637#[serde(rename_all = "snake_case", tag = "kind")]
638pub enum DurabilityPolicy {
639 SyncWriteThrough,
640 AsyncReplicated,
641 BufferedExport { max_loss_window_ms: u64 },
642}
643
644#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
646pub struct ContinuityHealth {
647 pub store_reachable: bool,
648 pub durability_policy: DurabilityPolicy,
649 pub last_checkpoint_version: Option<CheckpointVersion>,
650}
651
652#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
654pub struct IdentityStatus {
655 pub identity: AgentIdentity,
656 pub state: IdentityLifecycleState,
657 pub agent_runtime_id: Option<AgentRuntimeId>,
658 pub session_id: Option<meerkat_core::types::SessionId>,
659 pub profile: Option<meerkat_mob::ProfileName>,
660 pub runtime_mode: Option<meerkat_mob::MobRuntimeMode>,
661 pub addressability: AgentAddressability,
662 pub display_name: Option<DisplayName>,
663 #[serde(default)]
664 pub labels: std::collections::BTreeMap<String, String>,
665 pub generation: Option<ContinuityGeneration>,
666 pub checkpoint_version: Option<CheckpointVersion>,
667 pub lease: Option<LeaseInfo>,
668 pub continuity_health: Option<ContinuityHealth>,
669}
670
671#[derive(Clone, Default)]
676pub struct AgentRuntimeServices {
677 mob_handle: Option<meerkat_mob::MobHandle>,
678}
679
680impl AgentRuntimeServices {
681 pub fn new(mob_handle: meerkat_mob::MobHandle) -> Self {
682 Self {
683 mob_handle: Some(mob_handle),
684 }
685 }
686
687 pub fn empty() -> Self {
688 Self { mob_handle: None }
689 }
690
691 pub fn mob_handle(&self) -> Option<meerkat_mob::MobHandle> {
692 self.mob_handle.clone()
693 }
694
695 pub fn has_mob_handle(&self) -> bool {
696 self.mob_handle.is_some()
697 }
698}
699
700impl std::fmt::Debug for AgentRuntimeServices {
701 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
702 f.debug_struct("AgentRuntimeServices")
703 .field("mob_handle", &self.mob_handle.is_some())
704 .finish()
705 }
706}
707
708impl PartialEq for AgentRuntimeServices {
709 fn eq(&self, other: &Self) -> bool {
710 self.mob_handle.is_some() == other.mob_handle.is_some()
711 }
712}
713
714impl Eq for AgentRuntimeServices {}
715
716#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
718pub struct AgentBuildContext {
719 pub identity: AgentIdentity,
720 pub active_peers: Vec<AgentIdentity>,
721 pub managed_edges: Vec<ManagedPeerEdge>,
722 #[serde(default, skip)]
723 pub runtime_services: AgentRuntimeServices,
724}
725
726#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
728pub struct ExternalToolDef {
729 pub name: String,
730 pub description: String,
731 pub input_schema: serde_json::Value,
732}
733
734#[derive(Clone, Default)]
736pub struct LocalExternalToolOverlay {
737 dispatcher: Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>>,
738}
739
740impl LocalExternalToolOverlay {
741 pub fn new(dispatcher: Arc<dyn meerkat_core::agent::AgentToolDispatcher>) -> Self {
742 Self {
743 dispatcher: Some(dispatcher),
744 }
745 }
746
747 pub fn empty() -> Self {
748 Self { dispatcher: None }
749 }
750
751 pub fn dispatcher(&self) -> Option<Arc<dyn meerkat_core::agent::AgentToolDispatcher>> {
752 self.dispatcher.clone()
753 }
754
755 pub fn is_some(&self) -> bool {
756 self.dispatcher.is_some()
757 }
758}
759
760impl std::fmt::Debug for LocalExternalToolOverlay {
761 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
762 f.debug_struct("LocalExternalToolOverlay")
763 .field("dispatcher", &self.dispatcher.is_some())
764 .finish()
765 }
766}
767
768impl PartialEq for LocalExternalToolOverlay {
769 fn eq(&self, other: &Self) -> bool {
770 self.dispatcher.is_some() == other.dispatcher.is_some()
771 }
772}
773
774impl Eq for LocalExternalToolOverlay {}
775
776#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
782pub struct AgentBuildDraft {
783 pub model: Option<String>,
784 pub system_prompt: Option<String>,
785 #[serde(default)]
786 pub additional_instructions: Vec<String>,
787 #[serde(default)]
788 pub labels: std::collections::BTreeMap<String, String>,
789 pub app_context: Option<serde_json::Value>,
790 #[serde(default)]
791 pub external_tools: Vec<ExternalToolDef>,
792 #[serde(default, skip)]
793 pub local_external_tools: LocalExternalToolOverlay,
794}
795
796#[derive(Debug, Clone, PartialEq, Eq)]
805pub struct SessionSnapshot {
806 pub data: Vec<u8>,
807}
808
809impl Serialize for SessionSnapshot {
810 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
811 where
812 S: serde::Serializer,
813 {
814 use base64::Engine;
815 use serde::ser::SerializeStruct;
816 let encoded = base64::engine::general_purpose::STANDARD.encode(&self.data);
817 let mut s = serializer.serialize_struct("SessionSnapshot", 1)?;
818 s.serialize_field("data", &encoded)?;
819 s.end()
820 }
821}
822
823impl<'de> Deserialize<'de> for SessionSnapshot {
824 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
825 where
826 D: serde::Deserializer<'de>,
827 {
828 use base64::Engine;
829
830 #[derive(Deserialize)]
831 struct Wrapper {
832 data: String,
833 }
834
835 let wrapper = Wrapper::deserialize(deserializer)?;
836 let data = base64::engine::general_purpose::STANDARD
837 .decode(&wrapper.data)
838 .map_err(serde::de::Error::custom)?;
839 Ok(Self { data })
840 }
841}
842
843#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
849pub struct RosterContext {
850 pub mob_definition: Option<meerkat_mob::MobDefinition>,
851 pub previous_identities: Vec<AgentIdentity>,
852}
853
854#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
856pub struct TopologyContext {
857 pub roster: Vec<DurableAgentSpec>,
858}
859
860#[derive(Debug)]
866pub enum ContinuityStoreError {
867 StaleFencingToken {
868 identity: AgentIdentity,
869 presented: FencingToken,
870 current: FencingToken,
871 },
872 StaleCheckpointVersion {
873 identity: AgentIdentity,
874 presented: CheckpointVersion,
875 current: CheckpointVersion,
876 },
877 NotFound {
878 identity: AgentIdentity,
879 },
880 Io(String),
881 Corruption(String),
882}
883
884impl fmt::Display for ContinuityStoreError {
885 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
886 match self {
887 Self::StaleFencingToken {
888 identity,
889 presented,
890 current,
891 } => write!(
892 f,
893 "stale fencing token for {identity}: presented {presented}, current {current}"
894 ),
895 Self::StaleCheckpointVersion {
896 identity,
897 presented,
898 current,
899 } => write!(
900 f,
901 "stale checkpoint version for {identity}: presented {presented}, current {current}"
902 ),
903 Self::NotFound { identity } => {
904 write!(f, "continuity record not found for {identity}")
905 }
906 Self::Io(msg) => write!(f, "continuity store I/O error: {msg}"),
907 Self::Corruption(msg) => write!(f, "continuity store corruption: {msg}"),
908 }
909 }
910}
911
912impl std::error::Error for ContinuityStoreError {}
913
914#[derive(Debug)]
916pub enum LeaseError {
917 ProviderUnavailable(String),
918 Io(String),
919}
920
921impl fmt::Display for LeaseError {
922 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
923 match self {
924 Self::ProviderUnavailable(msg) => {
925 write!(f, "lease provider unavailable: {msg}")
926 }
927 Self::Io(msg) => write!(f, "lease I/O error: {msg}"),
928 }
929 }
930}
931
932impl std::error::Error for LeaseError {}
933
934#[derive(Debug)]
936pub enum RosterError {
937 ProviderUnavailable(String),
938 Io(String),
939}
940
941impl fmt::Display for RosterError {
942 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
943 match self {
944 Self::ProviderUnavailable(msg) => {
945 write!(f, "roster provider unavailable: {msg}")
946 }
947 Self::Io(msg) => write!(f, "roster I/O error: {msg}"),
948 }
949 }
950}
951
952impl std::error::Error for RosterError {}
953
954#[derive(Debug)]
956pub enum TopologyError {
957 InvalidEdge(String),
958 ProviderUnavailable(String),
959}
960
961impl fmt::Display for TopologyError {
962 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
963 match self {
964 Self::InvalidEdge(msg) => write!(f, "invalid topology edge: {msg}"),
965 Self::ProviderUnavailable(msg) => {
966 write!(f, "topology provider unavailable: {msg}")
967 }
968 }
969 }
970}
971
972impl std::error::Error for TopologyError {}
973
974#[derive(Debug)]
976pub enum CustomizerError {
977 BuildFailed(String),
978 Io(String),
979}
980
981impl fmt::Display for CustomizerError {
982 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
983 match self {
984 Self::BuildFailed(msg) => write!(f, "customizer build failed: {msg}"),
985 Self::Io(msg) => write!(f, "customizer I/O error: {msg}"),
986 }
987 }
988}
989
990impl std::error::Error for CustomizerError {}